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

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 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.
1 A Little Context
Rust began as a personal project at Mozilla and was first released as a stable 1.0 in 2015. 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. Get them wrong and the program simply does not compile. There is no middle ground, which is rather the whole point.
2 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.
fn main() {
let s = String::from("hello");
let len = calculate_length(&s); // borrow, don't take ownership
println!("'{}' is {} characters", s, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}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 and competes directly with C and C++. 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. Add rustfmt for formatting and clippy for lint suggestions, and the day-to-day experience feels modern rather than archaeological.
3 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.
let s1 = String::from("hello");
let s2 = s1; // ownership moves to s2
println!("{}", s1); // error: value borrowed after move
To a newcomer that error is baffling; the variable is right there. In time it makes sense, but the early frustration is real and it is many people’s first and last impression.
Compile times are the other recurring complaint. The compiler does an enormous amount of analysis, and that thoroughness has a price. A large project can take minutes to build, which interrupts the tight write-test-fix loop that keeps you in flow. The ecosystem, while growing fast, is still younger than those of Java or Python in places. For some specialised domains you will find the crate you need; for others you will be writing bindings yourself. 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.
4 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.
async fn fetch(url: &str) -> Result<String, reqwest::Error> {
let body = reqwest::get(url).await?.text().await?;
Ok(body)
}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.
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}Harmless here, but in real libraries these annotations multiply until a function signature needs its own explanatory comment. Finally, build times at scale become a recurring tax: a large codebase with heavy generics and macros can leave you watching the progress bar far more than you would like.
5 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.
6 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.