What Rust Memory Safety Does and Does Not Protect Explained
Safe Rust prevents memory corruption but not logic errors, panics, leaks, deadlocks or overflow. See exactly what is guaranteed, what is not and where unsafe code and dependencies reopen the gap.

"Memory safe" is a precise term, and the precision is why it is so often misquoted. It means that a specific set of bugs cannot occur in safe code. Everything else still can. Knowing the boundary tells you where to point your review effort. This post draws it with an example for each side. It is part of the threat model in security for Rust developers.
Rust 1.90, edition 2024.
What is guaranteed
In safe code, the compiler and the runtime checks rule out:
- Use after free and double free (ownership and lifetimes)
- Out-of-bounds reads and writes (bounds checks panic instead of reading)
- Data races on shared memory (
SendandSync, explained here) - Use of uninitialized memory (all bindings are initialized before use)
- Null pointer dereference (no null references;
Optionmakes absence explicit) - Invalid enum or
boolvalues created by safe code
fn main() {
let v = vec![1, 2, 3];
let i = 7;
println!("{}", v[i]);
}In C this reads past the buffer. In Rust it panics with index out of bounds: the len is 3 but the index is 7: defined behavior, and no memory is disclosed.
What is not guaranteed
Panics are a denial of service
The panic above stops the thread. If a request handler indexes with a user-supplied number, an attacker can crash workers at will, and with panic = "abort" the whole process. Memory safe, and still a vulnerability.
Leaks are allowed
use std::rc::Rc;
use std::cell::RefCell;
struct Node { next: Option<Rc<RefCell<Node>>> }
fn main() {
let a = Rc::new(RefCell::new(Node { next: None }));
let b = Rc::new(RefCell::new(Node { next: Some(a.clone()) }));
a.borrow_mut().next = Some(b.clone());
}The cycle is never freed. Rust treats leaks as safe because they cannot corrupt memory. A long running service that leaks per request still dies of memory exhaustion. See Weak for breaking cycles.
Overflow in release builds
Integer arithmetic wraps silently in release by default, and a wrapped length or price is a logic vulnerability. Use checked arithmetic where input controls the numbers: integer overflow bugs in release builds.
Logic and race conditions
Two requests that each check a balance and then withdraw can both succeed. That is a race on application state, not on memory. Deadlocks are likewise safe.
Injection and path bugs
format!("SELECT ... {}", input) is as injectable in Rust as in any language (SQL injection and typed queries). Joining a user path onto a base directory can escape it (path traversal).
Where the guarantee ends
- **
unsafeblocks.** The compiler trusts you (unsafe Rust explained). - Dependencies with unsafe. Soundness bugs in crates have been reported through RustSec; check your tree with
cargo geiger. - FFI. C and C++ libraries have no such guarantees.
- Compiler bugs. Rare, and tracked as soundness issues.
Using this in review
When reviewing Rust code, skip the questions the compiler answers and spend the time on: reachable panics from input, unbounded allocation, unsafe blocks and their comments, authorization logic, and what the dependencies do. The Rust project's own statement is in the Rustonomicon on safe and unsafe.


