Why Rust exists
Forty years of choosing between memory safety and control, and a compiler that refuses the choice.
The first time I tried to write Rust, the compiler rejected my code fourteen times in twenty minutes, and every rejection was an ownership violation, a kind of error I had never heard of. I didn't know I was violating anything. I just wanted to pass a string to a function and then use it again afterwards, and apparently, in Rust, that's a crime. I closed the editor and came back three days later to try again.
It wasn't even my first contact. I'd found Rust years earlier, down a compiler rabbit hole while studying electronics, and came back to it when Solana and Linus Torvalds made it impossible to ignore. Fair warning: this piece is pure idolatry. I have thick layers of concepts left to learn, and it's refreshing to see ideas that C and C++ seemed to have copyrighted reborn in a new paradigm.
That was roughly the start of one of the most rewarding (and most infuriating) learning experiences I've had as a programmer. And to understand why Rust works the way it does, you need to understand the problem it was built to solve, which is a really, really expensive one.
Seventy percent
Roughly 70% of all security vulnerabilities in large C and C++ codebases are memory safety bugs. The figure was measured more than once. Microsoft found that about 70% of the vulnerabilities it patched over twelve years were memory safety issues1, and Google's Chromium team, counting its 912 high and critical severity bugs since 2015, landed on the same number2. Android reported it too, for its high severity vulnerabilities3.
1Matt Miller, Trends, challenges, and strategic shifts in the software vulnerability mitigation landscape, BlueHat IL, 2019, summarised in the MSRC post A proactive approach to more secure code, 2019. 2The Chromium Projects, Memory safety, 2020. 3Jeff Vander Stoep and Stephen Hines, Rust in the Android platform, Google Security Blog, 2021.
A program has a memory safety bug when it touches memory it shouldn't have. It read from a place that had already been freed. It wrote past the end of a buffer. It dereferenced a pointer that pointed to nothing. It handed out two mutable references to the same data and then watched in horror as they stomped on each other.
Buffer overflows, a single category of memory bug, have been the root cause of some of the most devastating security breaches in computing history. Heartbleed (2014) and WannaCry (2017, spread through a memory corruption bug in Windows' SMBv1 server) both trace back to C code that read or wrote past where it should have. Heartbleed handed out up to 64 KB of a server's memory per request, keys and passwords included, because the code trusted a length it never checked.
Memory bugs are the ordinary way systems programs in C and C++ fail. If you write enough C, you will introduce them, however good you are at your job, because the language hands you a loaded gun with the safety off and asks you to try not to point it at your foot. The human brain is simply not good enough to manually track every allocation and its lifetime across a codebase of any meaningful size.
So for decades, the industry had two options, and both of them sucked.
Garbage collection or manual memory
The first option was garbage collection. Languages like Java and Go decided that humans can't be trusted with memory and took it out of their hands: a background process, the garbage collector, periodically scans the heap, finds objects nobody's using anymore, and frees them automatically. The cost turns up elsewhere. Garbage collectors introduce latency spikes. They consume memory overhead (sometimes substantial). They make performance unpredictable: your program might pause for 50 milliseconds at any moment while the GC does its thing. A web server usually shrugs that off. The firmware of a pacemaker cannot afford the pause at all.
Garbage collection is the right trade-off for most software. If you're writing a CRUD app or an API server, GC languages are great. The performance tax is small and the productivity gain is enormous. The problem is that some software can't afford the tax at all.
The second option was manual memory management, the answer of C and C++: you're the programmer, you know when memory should be allocated and freed, so do it yourself. This gives you total control and zero overhead. It also gives you use-after-free and an entire zoo of bugs that have cost the industry a fortune.
So you got safety and lost control, or got control and lost safety. For forty years, almost everyone outside a few research languages assumed those were the only two options1.
1Cyclone and the ML Kit had already tried region-based memory management, and Rust lists both among its influences.
A third option
In 2006, Graydon Hoare, a Mozilla engineer, started working on a side project. The story he tells starts with the elevator of his Vancouver building, out of order again because its software had crashed, and with the stairs he climbed knowing what kind of bug was behind it1. He was tired of the same categories of memory bugs in C and C++ showing up over and over, despite smart people trying hard to avoid them. So he started designing a language where those bugs would be structurally impossible: code that contains them would never compile, so nothing would be left for a code review to catch.
1Clive Thompson, How Rust went from a side project to the world's most-loved programming language, MIT Technology Review, 2023; Mozilla began sponsoring the project in 2009.
That language became Rust, and its core innovation is a concept called ownership.
Three rules
Rust's ownership system is built on three rules that sound almost trivially simple and have consequences that will break your brain.
Every value in Rust has exactly one owner. When the owner goes out of scope, the value is dropped (freed), with no garbage collector and no manual free(): the compiler inserts the cleanup code for you, at compile time, in exactly the right place.
Ownership can be moved from one variable to another, and once it moves, the original variable is dead; the compiler rejects any attempt to touch it. This is what caught me on day one. I passed a string to a function, which moved ownership into that function, and then tried to use the string afterwards, and Rust refused because the string was gone.
If you don't want to move ownership, you can borrow a value. A borrow is a reference, a pointer that doesn't own the data, and Rust enforces one ironclad constraint on it: either one mutable reference or any number of immutable references, never both at the same time. That single constraint eliminates data races at compile time.
That's the entire memory model. The compiler enforces safety without a garbage collector or manual memory management, and the generated code runs in the same league as C. The catch is that learning to work within these rules feels, at first, like writing code in a straitjacket.
Fighting the compiler
The borrow checker is the part of the Rust compiler that enforces the ownership rules. It is, simultaneously, the most hated and most loved feature of the language.
When you're new to Rust, the borrow checker is your enemy. You write perfectly reasonable-looking code. The compiler rejects it. You restructure. Rejected again. You google the error message. You read a blog post. You restructure again. Rejected again. You start questioning your career choices.
RUST Copy
fn main() { let mut data = vec![1, 2, 3]; let first = &data[0]; // immutable borrow data.push(4); // mutable borrow: REJECTED println!("{}", first); }
This code looks fine: you took a reference to the first element, then pushed a new element. But push can reallocate the vector's internal buffer. If it does, first is now pointing at freed memory. In C++, this compiles silently and produces a use-after-free bug that might crash your program three weeks later in production at 2am on a Sunday. In Rust, the compiler catches it before your code ever runs.
The borrow checker is showing you bugs you didn't know you were about to write.
Every time the compiler refuses, it's preventing a bug that would have existed in C++ or C. Rust clicks on the day you start reading each refusal as a bug report about your own design.
After a few weeks (or months, depending on your background), something changes, and the borrow checker begins to feel like a superpower. You realize that if your code compiles, an entire category of bugs cannot exist in it, use-after-free and data races among them.
The confidence this gives you is hard to overstate. You can refactor aggressively. You can write concurrent code without fear. You can hand your codebase to someone else and know that the compiler will catch their memory mistakes just like it caught yours.
The rest of the language
Ownership is the headline feature, but Rust has a bunch of other design choices that are quietly excellent.
There is no null in Rust. Instead, you have Option<T>, a type that is either Some(value) or None. The compiler forces you to handle the None case. You cannot forget. This eliminates null pointer exceptions, what Tony Hoare, who put the null reference into ALGOL W in 1965, called his billion-dollar mistake.
RUST Copy
fn find_user(id: u32) -> Option<User> { // returns Some(user) or None }
// the compiler forces you to handle both cases match find_user(42) { Some(user) => println!("found: {}", user.name), None => println!("user not found"), }
Rust has no exceptions either, and no try/catch. Errors are values: specifically, Result<T, E>, which is either Ok(value) or Err(error). You handle errors explicitly, in the type system, at every call site. The ? operator makes this ergonomic:
RUST Copy
fn read_config() -> Result<Config, io::Error> { let contents = fs::read_to_string("config.toml")?; // ? propagates errors let config = parse_toml(&contents)?; Ok(config) }
Rust's match is exhaustive: the compiler verifies you've handled every possible case. Combined with enums that can carry data (algebraic data types), this is absurdly powerful:
RUST Copy
enum Shape { Circle(f64), // radius Rectangle(f64, f64), // width, height Triangle(f64, f64, f64), // three sides }
fn area(shape: &Shape) -> f64 { match shape { Shape::Circle(r) => std::f64::consts::PI * r * r, Shape::Rectangle(w, h) => w * h, Shape::Triangle(a, b, c) => { let s = (a + b + c) / 2.0; (s * (s - a) * (s - b) * (s - c)).sqrt() } } // add a new variant to Shape? compiler error until you handle it here. }
Then there are the zero-cost abstractions. Iterators and closures compile down to the same machine code you'd write by hand in C, so you pay no runtime overhead for using high-level constructs; the compiler optimizes them away.
Who's shipping it
Rust runs in production at:
MozillaParts of Firefox's rendering engine, starting with Stylo, the CSS engine taken from Servo
AmazonFirecracker (the VM that powers Lambda and Fargate)
MicrosoftPorting parts of Windows from C++, DWriteCore and the Win32 GDI among them
GoogleAndroid's Bluetooth stack, parts of Fuchsia
CloudflarePingora, their HTTP proxy, serving over a trillion requests a day
DiscordRewrote their read states service from Go to Rust for predictable latency
Linux kernelRust support arrived in Linux 6.1 in 2022, and in December 2025 the maintainers declared the experiment over (this is a massive endorsement)
The pattern is consistent: organizations that need C-level performance with fewer bugs are reaching for Rust, and what it replaces is C and C++, rarely Python or JavaScript.
The pain is the point
What took me the longest to understand about Rust is that Graydon Hoare and the Rust team left the difficulty in on purpose, because the difficulty is the language telling you something true about your problem. When the borrow checker rejects your code, it is revealing a real tension in your design: two parts of your program want conflicting access to the same data, and you haven't resolved that conflict. In other languages the same conflict exists and stays out of sight until production.
Rust makes the implicit explicit. It forces you to think about who owns each piece of data up front, at design time, rather than discovering those issues later as bugs. It's the programming language equivalent of measure twice, cut once, except the ruler screams at you if you try to cut without measuring.
Rust is not the right tool for every job. If you're prototyping, or your team doesn't have time for the learning curve, use Python or Go. Rust's value proposition is specific: maximum performance with maximum safety, at the cost of initial development speed. Know when that trade-off makes sense.
I still get rejected by the borrow checker. Regularly. The difference is that now, when it says no, my first instinct is curiosity about what I'm not seeing, some conflict in my design that I haven't noticed yet. The compiler has usually noticed it already, and slowly, painfully, it teaches you to see it too.