Rust, the good, bad and ugly
Fearless concurrency at the cost of a borrow checker

Contents
Rust is a systems programming language that has spent the better part of a decade topping developer surveys for being the most loved and, in roughly the same breath, the most intimidating. It has led the “most admired language” spot in Stack Overflow’s developer survey for years running, which is a strange badge for a language people also describe as the hardest they’ve ever picked up. It promises the performance of C and C++ with none of the dreaded segfaults, buffer overflows, or use-after-free bugs, and it delivers on that promise through a famously strict compiler. Whether that strictness feels like a guardian angel or a bureaucratic nightmare depends entirely on the day. Let us walk through the good, the bad, and the ugly.
I’ve shipped Rust in anger and I’ve also thrown my keyboard across the room over a lifetime annotation, so this is not a fan letter. It’s the same treatment I’ve given other languages in this series — see the equivalent for Go and for Python — and Rust earns both more praise and more scorn than most.
A Little Context
Rust began as a personal project by Graydon Hoare at Mozilla in 2006, was adopted by Mozilla in 2009, and was first released as a stable 1.0 in May 2015. When Mozilla laid off much of the Rust team in 2020, stewardship passed to the independent, non-profit Rust Foundation, backed by companies including AWS, Google, Microsoft and others — a transition that, unusually for a corporate-born language, seems to have left it healthier rather than orphaned.
Its central idea is that memory safety should be guaranteed at compile time, without a garbage collector running in the background to clean up after you. The mechanism for this is ownership, a set of rules the compiler enforces about who is allowed to use a piece of data and when. Get those rules right and your program is provably free of an entire category of bugs — the same class of memory-corruption bugs that, by Microsoft’s and Google’s own published figures, account for roughly 70% of serious security vulnerabilities in large C and C++ codebases. Get them wrong and the program simply does not compile. There is no middle ground, which is rather the whole point.
The Good
The headline feature is memory safety without a garbage collector. Rust tracks ownership of every value, so it knows exactly when memory can be freed and inserts the cleanup automatically. No runtime pauses, no manual free, no dangling pointers.
| |
That & is a borrow: calculate_length may read s but does not own it, so s is still usable afterwards. The borrow checker enforces these rules at compile time, which is where Rust’s famous fearless concurrency comes from. The same machinery that prevents two parts of your code from misusing memory also prevents two threads from racing on the same data. If it compiles, it generally does not have a data race, and that confidence transforms how you write parallel code.
Performance is excellent: Rust compiles to native machine code with no runtime and no GC pauses, and competes directly with C and C++. The zero-cost abstractions promise is largely real — an iterator chain or a pattern match compiles down to roughly the same instructions you’d have written by hand, so you get high-level ergonomics without a performance tax. That combination is why Rust has landed in the Linux kernel, in Android’s low-level components, and in the guts of tools like ripgrep and the Deno runtime.
The tooling is a genuine joy, and unusually so for a systems language. Cargo, the build tool and package manager, handles dependencies, building, testing, and documentation with one consistent command-line interface — cargo build, cargo test, cargo doc, cargo add, and you’re done. Anyone who has fought CMake or Autotools will understand what a relief this is. Add rustfmt for formatting and clippy for lint suggestions, and the day-to-day experience feels modern rather than archaeological. clippy in particular is unusually good: it doesn’t just flag style, it teaches you idiomatic Rust as you go.
The type system deserves its own mention. Algebraic data types via enum, exhaustive match, and the Option/Result pair mean “this value might be missing” and “this might fail” are encoded in the types you can’t ignore. There is no null to forget to check and no silent exception to swallow. The compiler forces you to handle the sad path, which is precisely the path that causes production incidents in less strict languages.
The Bad
The learning curve is steep, and there is no polite way around it. Concepts such as ownership, borrowing, and lifetimes have no direct equivalent in most languages, so experienced programmers find themselves back at the bottom of the hill, arguing with a compiler that rejects code which would compile instantly elsewhere.
| |
To a newcomer that error is baffling; the variable is right there. In time it makes sense, but the early frustration is real, and for a good number of developers it is their first and last impression of the language. The move-semantics model — that assigning a value transfers ownership rather than copying or aliasing it — is genuinely unlike the mental model you bring from almost anywhere else, and no amount of “it clicks eventually” softens the first month.
Compile times are the other recurring complaint. The compiler does an enormous amount of analysis — borrow checking, monomorphising every generic, running LLVM’s optimiser — and that thoroughness has a price. A large project can take minutes to build from clean, which interrupts the tight write-test-fix loop that keeps you in flow. There are mitigations (incremental compilation, sccache, splitting into smaller crates, using the faster mold or lld linker), but you will spend time learning them, and they’re a tax you don’t pay in an interpreted language at all.
The ecosystem, while growing fast, is still younger than those of Java or Python in places. For some specialised domains you will find a mature, well-maintained crate; for others you will be writing bindings yourself or picking between three half-finished attempts. GUI development in particular is still visibly unsettled. And Rust is verbose: explicit error handling, explicit types at boundaries, and the occasional thicket of generics mean more keystrokes than a scripting language demands. Result propagation is eased enormously by the ? operator, but you still write more ceremony than the equivalent Python — the trade is that the ceremony is doing safety work, not busywork.
The Ugly
Then there is fighting the borrow checker, the rite of passage every Rust developer endures. You write something perfectly reasonable, the compiler refuses it, and you spend twenty minutes restructuring code to satisfy rules you only half understand. The error messages are genuinely helpful, among the best in any language, but that does not make the wall any less real when you hit it.
Async is where the ugliness compounds. Asynchronous Rust is powerful but notoriously intricate. You must pick a runtime, wrestle with Pin, Send, and Sync bounds, and decode lifetime errors that span several layers of Future.
| |
That looks tidy enough, until a lifetime from an awaited borrow tangles with a spawned task and the compiler emits a paragraph of trait-bound diagnostics. Which brings us to trait and lifetime soup: signatures that accumulate so many generic parameters, where clauses, and explicit lifetimes ('a, 'b) that the actual logic vanishes beneath the annotations.
| |
Harmless here, but in real libraries these annotations multiply until a function signature needs its own explanatory comment. Higher-ranked trait bounds — the for<'a> incantation — are where even experienced Rust developers admit they cargo-cult the syntax until it compiles.
There’s also the unsafe escape hatch, which is honest rather than ugly but deserves naming. Rust lets you opt out of the borrow checker inside an unsafe block for the cases where you genuinely know better than the compiler — FFI, certain data structures, hardware access. The danger is cultural: unsafe doesn’t turn off all checks, only a specific few, and misunderstanding exactly which ones is how memory-safety guarantees quietly leak away. A single sloppy unsafe block can reintroduce the very bugs the language exists to prevent, and it will do so without a warning.
Finally, build times at scale become a recurring tax: a large codebase with heavy generics and procedural macros can leave you watching the progress bar far more than you would like. Macro-heavy frameworks compound this, because the compiler expands and re-checks generated code you never see.
Who Rust Is Really For
It is worth being honest about where Rust earns its keep, because the answer is not “everywhere”. The language was built for a specific kind of problem: software where a bug is expensive, where performance is not negotiable, and where the same code may run for years on machines you will never touch. Operating-system components, browser engines, embedded firmware, cryptography, game engines, and high-throughput network services all fit that shape. In those domains the up-front cost of satisfying the borrow checker is dwarfed by the cost of a memory-corruption vulnerability discovered in production.
There is also a cultural pull worth mentioning. Rust attracts developers who enjoy precision, and its community has invested heavily in documentation, the freely available official book chief among them, so help is rarely far away. That same community can be intense about correctness, which is mostly a strength and occasionally a source of bikeshedding. For a quick internal script, a data-analysis notebook, or a prototype you expect to throw away next week, Rust is the wrong tool and will feel like wearing a spacesuit to pop to the shops. Reach for it when the cost of being wrong is high, and it rewards you handsomely; reach for it out of fashion alone, and you will mostly experience the friction without the payoff.
The Verdict
Rust asks a great deal of you up front and pays it back over the lifetime of the project. The borrow checker that infuriates you in week one is the same borrow checker that lets you refactor a concurrent system in month six without lying awake worrying about data races. The compile times are real, the learning curve is real, and the lifetime soup is occasionally indigestible, but none of it is gratuitous. Each rough edge is the cost of a guarantee that other languages simply cannot make.
So the honest summary is this: Rust is not the right tool for a quick script or a throwaway prototype, where its ceremony only slows you down. It is a superb choice when correctness, performance, and concurrency genuinely matter, for systems software, infrastructure, and anything where a memory bug is a security incident rather than an inconvenience. Whether the borrow checker is good, bad, or ugly depends, as ever, on what you are building and how much you value sleeping soundly at deploy time.




