There is a figure the industry has been repeating for years and that rarely makes the headlines: around 70% of the serious vulnerabilities Microsoft patches every month share the same origin. They are not logic flaws or badly chosen passwords; they are memory errors. A program reads an area that has already been freed, writes a byte past the end of a buffer, or two threads modify the same word at the same time without agreeing on it. Rust proposes moving the check into the compiler and taking the key to the padlock away from the programmer. The price is very concrete: before it lets you compile, the compiler forces you to explain who owns each byte.
The bug that is born from three legal lines
Three lines of C, all perfectly legal as far as the compiler is concerned: p = malloc(16); free(p); *p = 42;. The first asks the heap for 16 bytes, the second hands them back, and the third writes to an address that is no longer yours. The language says nothing, because in C’s model a pointer is nothing more than an integer holding an address. Nothing in the type int * says whether that block is still alive, how many bytes it holds, or who else is using it.
Three classic families come out of that. A use-after-free reappears when the allocator has already handed that slot to another part of the program: you end up writing inside someone else’s object. A double free corrupts the allocator’s internal lists and usually turns into an arbitrary write, the foundation of half a dozen real exploits. A buffer overflow is the consequence of the block size existing only in the programmer’s head. And when two threads step on each other you get a data race: undefined behaviour, which means the compiler may do anything it likes with your code, including deleting the part you thought was necessary.
Ownership: a single owner, no discussion
Rust starts from a rule that fits in one sentence: every value has exactly one owner, and when the owner leaves its scope the value is freed. In code, let s = String::from("hola"); let t = s; compiles, but from that point on s ceases to exist for the compiler: the assignment was a move, not a copy. If you try to use s afterwards you will not get a runtime failure, you will get a compile error. The reason is straightforward: a buffer lives on the heap, so two variables holding the same pointer would mean freeing the same block twice once both of them went out of scope.
The classic alternative is a garbage collector, either counting references or sweeping memory from time to time. Rust chooses the opposite: deterministic destruction. The compiler inserts the call to drop exactly where it belongs, even if a return or an early exit with ? happens in between. That is what lets a file be closed, a lock be released or a socket be shut without anyone writing the code for it: the release is tied to the type, not to your discipline. And null disappears: there is no empty reference, there is Option<T>, a type that forces you to handle the missing value before moving on.
Borrows: read or write, but not both at once
The second pillar is the borrow. Instead of handing over ownership, you lend it: &T is a shared borrow and many can exist at the same time; &mut T is exclusive and only one may exist, with no other live borrow. The rule is summarised as aliasing or mutation, never both. That is exactly the point where C and C++ run out of safety net: at any instant when two names point at the same thing and somebody writes, the outcome depends on things the language cannot prove.
The piece that makes this practical is the borrow checker, a static analysis over the program’s control-flow graph. For every reference it computes its lifetime, the region of code where it is still valid, and it rejects the program if an incompatible access falls inside that region. Since Rust 1.63 those regions are no longer brace-delimited blocks but stretches of actual use: these are non-lexical lifetimes. It shows best with the classic example, iterating and modifying at the same time: let p = &v[0]; v.push(4);. The borrow p points at the vector’s inner buffer, and push may reallocate it as it grows and move the data elsewhere. In C++ that is silent undefined behaviour — the famous iterator invalidation — whereas here it is an error that never compiles.
The same holds for concurrency. A data race needs exactly two ingredients: overlapping accesses and at least one write. Because the compiler already forbids that combination between borrows, data shared across threads can only travel through types that guarantee it: Arc provides shared ownership but grants read-only access, and to modify it you must wrap it in a Mutex or an equivalent primitive. The Send and Sync traits mark which types may cross from one thread to another, and they are checked in that same analysis.
The engine is not finished. The third generation, Polonius, rewrites the analysis as a set of Datalog-style rules over the graph, and it is meant to accept correct programs that are rejected today: the typical case being the function that returns a reference to borrowed data.
What the compiler will not forgive, but does allow
Saying that Rust eliminates memory bugs without nuance would be snake oil. The keyword unsafe disables several of those checks and remains essential to talk to hardware or to libraries written in C; raw pointers come back there, and the bugs with them. Interior mutability (Cell, RefCell) moves the check to runtime: a double mutable borrow is no longer undefined behaviour, it is an immediate panic, which is better, but it is also a program crashing in production. And one case is surprising: with no collector there is no cycle collector, so two Arc handles pointing at each other are never freed. That is memory-safe, but it is still a leak.
None of this changes anything about logic flaws either: SQL injection, poor permission handling or a private key written to the log remain just as easy to commit. Memory safety is one class of defect that is demonstrably absent, not a guarantee of correctness. And it is not free: longer compile times, a real learning curve, and structures that C solves with two pointers — graphs, doubly linked lists, trees with a parent pointer — which here require indices, arenas or Pin.
The numbers that can actually be measured
The argument stops being philosophical when you look at Android. In 2019, 76% of the system’s vulnerabilities had memory-related causes; by 2024 Google put that at 24%, with new code written mostly in Rust and a density of this kind of bug per line roughly a thousand times lower than in C and C++. The following year the share fell below 20%.
In the Linux kernel, Rust entered as scaffolding in version 6.1, in December 2022, and the first Rust graphics driver reached the mainline with 6.15, in May 2025. That is no accident: drivers are where the kernel’s memory bugs pile up, because they are the part that talks directly to the hardware. Microsoft has followed the same route in Windows, with Surface drivers written in Rust on top of abstractions over the C interfaces, and in 2026 it extended the model to C# by making unsafe regions explicit and scoped. Regulatory pressure pushes the same way: the US CISA guidance on memory safety roadmaps asked manufacturers of critical software to publish their plan before 1 January 2026.
A pointer with a lawyer, not a faster language
The most useful way to look at Rust is not as a faster language but as a change in when the bill is paid. A memory bug is discovered today four years after it was written, while an exploit is being analysed, and it costs an emergency patch and sometimes an incident. Rust discovers it on the machine of the person who wrote it, while the coffee goes cold. It does not fix programs: it makes an entire class of errors fail to exist, and that, with 70% of the vulnerability catalogue pointing at the same place, is why the people who sign the patches are adopting it.






