Hi, I’m Denis Gladkiy, a software engineer at Huly Labs with a background in computer graphics and two decades of experience in C++, Java, and C#. A month ago, I began my journey with Rust. In part one of this series, I wrote about the things I genuinely liked.
But this post is about the other side of the coin: the things that puzzled or frustrated me in Rust. Not from a place of bitterness — but from curiosity, comparison, and experience.
A Regression in Integer Type Design
Rust’s treatment of integral types feels like a step back. There’s u8
, u16
, u32
, u64
, usize
, and their signed counterparts. These types are not implicitly compatible, and trying to do arithmetic between them often results in type mismatch errors and the need for explicit casting.
Sure, that’s safer from a systems-level perspective. But languages like Java and C# solved this problem years ago with a pragmatic default: a single integer type (int
) for 95% of cases, with more specialized types used only when really needed.
Rust’s approach may be theoretically sound, but from a developer experience standpoint, it’s a clear regression — forcing verbosity and friction for something that’s already well-understood.
The Bird Language
fn
, len
, ref
, mut
, pub
, dyn
… Rust syntax often feels like someone was trying to compress a novel into a tweet. At the same time, we get full words like struct
and trait
, which makes the abbreviation pattern feel inconsistent.
I get that many of these terms are legacy or inspired by functional languages. But in 2025, with abundant screen space and memory, do we really need to pretend we’re writing for punch cards?
Local Lingo Overload
Rust comes with its own glossary of terms: crate, Box, cargo, .toml
files — all of which make the language feel more clubhouse than industrial. There’s a learning curve not just in the language itself, but in adopting the Rust culture and vocabulary.
To a newcomer, it can feel a bit too niche. There’s value in distinct terms, but there’s also a cost in clarity.
The Build System: Surprisingly DIY
While I praised the fact that build scripts can be written in Rust itself, the build system as a whole still feels immature. Cargo is great for package management, but when you need to do anything even slightly non-standard, you find yourself hand-coding things that are trivial in more mature build ecosystems.
Yes, it works. But you’re going to write a lot of glue code that in other environments would be handled by configuration or convention.
Macros: The Wrong Kind of Power
Rust macros are powerful, but they feel more like a workaround than a language feature. Instead of a clean, structured system for compile-time programming (like in D), you get a syntax tree hacking tool that’s both hard to read and harder to debug.
I don’t mind metaprogramming, but I want it to be part of the language, not an almost-separate domain with its own rules.
Type Inference That’s Too Smart
Rust’s type inference is aggressive — and sometimes too clever for its own good. I’ve had cases where two variables, declared identically within the same function, ended up with completely different types based solely on how they were used later.
On a language that’s otherwise extremely strict and safe, this kind of invisible complexity feels out of place. It breaks the mental model. I’d rather write an extra type annotation than be surprised by inference behaving like a mind reader.
The Testing Framework: Barebones
I love the idea of compiling and testing code embedded in documentation. But beyond that, the built-in testing framework is very basic. Coming from NUnit and the C# ecosystem, Rust’s testing features feel underpowered and rudimentary.
Assertions, setup/teardown logic, parameterized tests — everything requires boilerplate or external crates. It gets the job done, but barely.
Tuple Field Naming: Still Not Allowed
One very specific (but surprisingly annoying) limitation: there’s no way to name the fields of a tuple struct. You’re stuck with .0
, .1
, etc., even in cases where semantic names would make your code vastly more readable. I hope this changes eventually.
Operator Overloading Shouldn’t Exist
Let’s be clear: operator overloading in Rust exists — but I wish it didn’t.
This feature encourages clever but opaque code, often hiding real behavior behind symbolic noise. Overloading +
, ==
, or *
might seem elegant for math-heavy types, but in practice it leads to ambiguity and readability issues — especially when mixed with traits and generics.
Rust prides itself on clarity and explicitness. Operator overloading, in my view, violates that principle. It’s not just poorly implemented — it’s a feature that should not exist in the first place. If behavior matters, it should be expressed through clearly named methods, not hidden in overloaded symbols.
Mutable Strings: Solving an Already Solved Problem
Another example of unnecessary complexity: mutable strings. In Rust, String
and &str
are two different types. Ownership, borrowing, lifetimes — these all come into play even for basic string manipulation.
The deeper issue, though, is philosophical. Over the past decades, mainstream languages like Java, C#, Python, and even JavaScript have converged on immutable strings by default — not because it’s fashionable, but because it’s safer, simpler, and just makes sense.
Rust, by contrast, treats String
as mutable and expects you to manage that mutability explicitly. It may be technically safe, but conceptually it feels like a regression — reopening a problem the industry already spent years closing.
Missing Little Conveniences
Sometimes it’s the little things. There’s no nameof()
equivalent, for example — a seemingly small omission, but one that I feel surprisingly often when doing logging or diagnostics. A few conveniences from more high-level languages wouldn’t hurt.
The Tooling Feels Early
The core tools — compiler, package manager — are solid. But once you step into more advanced workflows, things start to feel raw. For example, there’s no good way to customize how types appear in the debugger. That kind of polish matters — especially when you’re trying to debug large systems.
Final Thoughts
Rust is clearly a powerful, modern language. But it’s also young. Some of its pain points stem from conscious design trade-offs; others feel more like growing pains. As a newcomer, I often found myself admiring its elegance and clarity — only to run into sharp corners just when things were starting to flow.
And that’s okay. Every language has rough edges. What matters is whether the trade-offs make sense in the context of your goals.
Despite all the above, I’m still optimistic about Rust. The fact that I want to complain about these things — rather than just walk away — is a good sign. It means there’s something worth sticking with.