Rust's Ownership Model: Memory Safety Without a Garbage Collector

Black-and-white photograph by Matt Artz
Photograph by Matt Artz, via Unsplash.

Roughly 70% of severe security bugs in C and C++ are memory-safety violations. Rust's ownership system eliminates them at compile time, with no runtime collector — and the industry is finally moving.

For thirty years, systems programming has lived with a tradeoff that felt fundamental: manual memory management and the bugs that come with it, or a garbage collector and the latency it imposes. Rust refused the choice. Its ownership model gives the safety of a managed language and the layout control of C, paid for entirely at compile time. The mechanism is a small set of rules — and a compiler stubborn enough to enforce them.

The Cost That Made the Idea Necessary

Memory-safety bugs are not an aesthetic complaint. They are the dominant source of remotely exploitable security vulnerabilities in software written in C and C++. Microsoft’s Security Response Center quantified it:

“~70% of the vulnerabilities Microsoft assigns a CVE each year continue to be memory safety issues.” — Matt Miller, Trends, Challenges, and Shifts in Software Vulnerability Mitigation, BlueHat IL, February 2019.

Google’s Chromium team independently reports the same proportion:

“Around 70% of our serious security bugs are memory safety problems.” — The Chromium Projects, Memory safety, ongoing.

In February 2024 the U.S. White House’s Office of the National Cyber Director went further than describing the problem:

“Memory safety vulnerabilities are a class of vulnerability affecting how memory can be accessed, written, allocated, or deallocated. Most importantly, memory safety vulnerabilities are preventable. The most effective way to eliminate this entire class of vulnerabilities is for software manufacturers to adopt memory-safe programming languages.” — ONCD, Back to the Building Blocks: A Path Toward Secure and Measurable Software, February 2024.

The empirical confirmation arrived from Android. By writing new platform code in Rust while leaving legacy C/C++ untouched, Google’s Android team observed memory-safety vulnerabilities fall from 76% of Android’s annual vulnerabilities in 2019 to 24% in 2024:

“The percentage of memory safety vulnerabilities in Android has decreased from 76% in 2019 to 24% in 2024, well below the 70% industry norm.” — Jeff Vander Stoep, Eliminating Memory Safety Vulnerabilities at the Source, Google Security Blog, September 2024.

Older code keeps decaying; the new code is simply not generating new bugs in this class.

This is the backdrop against which ownership exists. It is not a language feature. It is a security argument.

The Three Rules

Every value in Rust is governed by three rules:

  1. Each value has exactly one owner (a binding).
  2. When the owner goes out of scope, the value is dropped — its destructor runs and its memory is released.
  3. References to a value obey aliasing XOR mutability: at any moment there is either one mutable reference, or any number of immutable references, but never both.

The first two rules give deterministic destruction (RAII inherited from C++). The third — exclusivity of mutable access — is the original contribution, and the reason data races and most iterator-invalidation bugs are statically impossible.

Formally, for any two live references to the same value:

The compiler proves this property for every reference in the program. There is no runtime check.

The full proof that the rules are sound — that safe Rust really cannot produce undefined behaviour — was given by the RustBelt project, which formalised a substantial subset of the language and its standard library in the Coq proof assistant.

“RustBelt is the first formal verification of the Rust language, verifying not only the type system but also a representative collection of libraries in Rust’s standard library.” — Jung, Jourdan, Krebbers, Dreyer, RustBelt: Securing the Foundations of the Rust Programming Language, POPL 2018.

Move Semantics and RAII

fn main() {
    let s1 = String::from("hello");
    let s2 = s1; // s1 is MOVED into s2

    // println!("{}", s1); // error[E0382]: borrow of moved value

    println!("{}", s2); // s2 is the owner; on scope exit, dropped exactly once
}

The assignment let s2 = s1 does not copy the heap buffer; it transfers ownership. The compiler then tracks that s1 is uninitialised. When s2 leaves scope, Drop::drop runs once. There is no reference counting, no tracing collector, no free written by hand.

This is the same RAII pattern that C++ has had since 1985 — but Rust enforces it. In C++, a careless copy constructor or a missed std::move will double-free or leak. In Rust, the borrow checker refuses to compile either case.

Borrowing: Aliasing XOR Mutability

A reference in Rust comes in two flavours:

FormNotationCapability
Shared (immutable)&TAny number simultaneously; read-only
Exclusive (mutable)&mut TAt most one at a time; read and write

The borrow checker enforces these as a static type-state on every binding. The payoff is not just memory safety: shared &T references can be assumed by the optimiser to be aliasing-free with any &mut T to the same memory. This is the noalias annotation LLVM has always wanted from C but cannot generally trust. Rust hands it over by construction.

The cost is the famous learning curve. Patterns that compile trivially in Java or Go — a doubly linked list, a graph with parent pointers, a closure that captures a mutable reference past its enclosing scope — are not expressible without unsafe, Rc<RefCell<T>>, or a redesign. The community has converged on the verdict that this is a feature, not a bug: those patterns were usually the bugs.

Lifetimes

A reference must not outlive the data it points to. Rust expresses this with lifetime parameters, generic over scope:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

The signature declares that the returned reference borrows from the same region 'a as the inputs. Relationships between lifetimes use subtyping: 'a: 'b means 'a outlives 'b. Most lifetime annotations are inferred by elision; explicit annotations appear only when the relationship between inputs and outputs is genuinely ambiguous.

Lifetimes are erased at compile time. They produce no code and consume no memory at runtime. They exist only as a proof obligation discharged by the type checker.

Send, Sync, and Fearless Concurrency

Two marker traits, Send and Sync, extend ownership reasoning to threads:

  • T: Send — values of type T may be transferred across thread boundaries.
  • T: Sync&T may be shared across threads (equivalently, &T: Send).

These traits are auto-derived from a type’s structural composition. Rc<T> (non-atomic reference count) is !Send because two threads concurrently decrementing the count would race. Arc<T> is Send + Sync because the count is atomic. The compiler refuses, at compile time, to spawn a thread that captures a non-Send value.

Combined with aliasing-XOR-mutability, this produces what the Rust community calls fearless concurrency: the same borrow checker that prevents iterator invalidation in single-threaded code prevents data races in multi-threaded code, and for the same reason.

What This Costs

The ownership model has costs that any honest treatment must list.

  • Async lifetimes. Composing borrows across await points runs into the Pin and self-referential generator problem, which the language has not fully resolved. Async functions in traits, stabilised at the end of 2023, still have ergonomic gaps that more advanced patterns work around with crates such as async-trait.
  • Linked data structures. Implementing classic graph or tree structures without unsafe requires arena allocators or indices instead of pointers. Correct, but not what a C programmer expects.
  • Compile time. Monomorphisation plus borrow analysis makes Rust slow to compile. Clean builds of a non-trivial workspace are measured in minutes, not seconds.
  • Learning curve. Engineers fluent in another systems language typically spend several months before they stop fighting the borrow checker.
  • unsafe. A 2020 corpus study found that roughly 29% of crates on crates.io contain at least one unsafe block, and the standard library is extensively unsafe internally. Ownership is a guarantee about safe Rust; unsafe is the trapdoor where the guarantee does not hold and where human review must take over.

None of these are fatal. All of them are real.

Where Rust Stands Now

SurfaceStatus
Linux kernelRust support merged in 6.1, October 2022; driver and rust-for-linux subsystem growing
WindowsRust modules in DWriteCore, Win32 GDI / GDI+ region engine, and parts of win32k (2023–24)
Android platformNew native code in Rust; memory-safety CVEs down from 76% (2019) to 24% (2024)
ChromiumRust accepted for third-party libraries since January 2023; first-party use growing
AWS Firecracker, BottlerocketProduction VMM and OS for AWS Lambda and Fargate, written in Rust
Cryptography & TLSrustls, ring, dalek-cryptography, RustCrypto; audited and in production
Embeddedembedded-hal ecosystem stable; Tock OS in production, Hubris in Oxide Cloud Computer hardware

The endorsement most often quoted in industry circles came from Microsoft’s Azure CTO:

“Speaking of languages, it’s time to halt starting any new projects in C/C++ and use Rust for those scenarios where a non-GC language is required. For the sake of security and reliability, the industry should declare those languages as deprecated.” — Mark Russinovich, September 2022.

CISA, the NSA, and the ONCD have all published guidance recommending memory-safe languages for new code in critical systems. Rust is the only systems-grade language in that category with production-scale deployment evidence.

The Quiet Part

The ownership model is often described as a clever trick. It is not. It is the operational consequence of a much older idea: linear types, from Girard’s linear logic (1987) and Wadler’s “Linear Types Can Change the World!” (1990) — type systems that track resource use so that values cannot be silently duplicated or discarded. What Rust did was not invent the theory. It made the theory ergonomic enough that systems programmers would accept it.

That part took twenty years. The borrow checker is the easy half; the syntax, the error messages, the editor integration, the standard library, and the cultural insistence that compiling code is the floor and not the ceiling — those are what made adoption possible. Every other attempt at linear types in a mainstream language failed not because the type system was wrong, but because the experience of using it was unbearable.

“Cryptography is rarely broken; it is bypassed.” — Adi Shamir

The same is true of safety in systems code. C and C++ are not insecure because their type systems are weak. They are insecure because the safe path requires a vigilance no human sustains across a million lines. Ownership removes the requirement.

References

  1. Miller, M. Trends, Challenges, and Shifts in Software Vulnerability Mitigation. BlueHat IL, Microsoft Security Response Center, February 2019. github.com/Microsoft/MSRC-Security-Research
  2. The Chromium Projects. Memory safety. www.chromium.org/Home/chromium-security/memory-safety
  3. Office of the National Cyber Director. Back to the Building Blocks: A Path Toward Secure and Measurable Software. The White House, February 2024. bidenwhitehouse.archives.gov/…/Final-ONCD-Technical-Report.pdf
  4. Vander Stoep, J. Eliminating Memory Safety Vulnerabilities at the Source. Google Security Blog, September 2024. security.googleblog.com
  5. NSA. Software Memory Safety Cybersecurity Information Sheet. November 2022. media.defense.gov
  6. Jung, R.; Jourdan, J.-H.; Krebbers, R.; Dreyer, D. RustBelt: Securing the Foundations of the Rust Programming Language. POPL 2018. plv.mpi-sws.org/rustbelt
  7. Astrauskas, V.; Matheja, C.; Poli, F.; Müller, P.; Summers, A. How Do Programmers Use Unsafe Rust? OOPSLA 2020. doi.org/10.1145/3428204
  8. Girard, J.-Y. Linear Logic. Theoretical Computer Science 50(1), 1987.
  9. Wadler, P. Linear Types Can Change the World! In Programming Concepts and Methods, North-Holland, 1990.
  10. Klabnik, S.; Nichols, C. The Rust Programming Language. No Starch Press / rust-lang.org. doc.rust-lang.org/book
  11. Torvalds, L. Merge of initial Rust support, Linux 6.1, October 2022. git.kernel.org
  12. Russinovich, M. Public statement on Rust adoption, September 19, 2022. twitter.com/markrussinovich/status/1571995117233504257