Unsafe Rust
Safe vs. Unsafe Rust
Rust is partitioned into two dialects that coexist within the same source file: Safe Rust (the default language where every operation is verified by the compiler to be free of undefined behavior) and Unsafe Rust (entered via the unsafe keyword, which unlocks five additional low-level capabilities called unsafe superpowers).
The Five Unsafe Superpowers
Entering an unsafe block or unsafe fn unlocks only these five operations and nothing else:
| # | Unsafe Superpower | Safe Rust Restriction | Unsafe Rust Capability |
|---|---|---|---|
| 1 | Dereference raw pointers | Can create *const T and *mut T, but cannot dereference *ptr |
Can read and write memory via *raw_ptr |
| 2 | Call unsafe functions or methods |
Cannot call unsafe fn or extern FFI functions |
Can call unsafe fn and foreign C functions |
| 3 | Access or modify a mutable static variable |
Cannot read or write static mut items |
Can read or write static mut items (prefer atomics/Mutex instead) |
| 4 | Implement an unsafe trait |
Cannot write impl UnsafeTrait for T |
Can write unsafe impl UnsafeTrait for T (such as Send, Sync, GlobalAlloc) |
| 5 | Access fields of a union |
Can initialize or assign to a union field, but cannot read a field |
Can read my_union.field (assuming active bit validity) |
What Remains Checked Inside an unsafe Block
An unsafe block does not turn off the borrow checker, lifetime checker, type checker, visibility rules, or drop glue for normal Rust references (&T and &mut T). If you borrow a variable mutably and immutably at the same time using safe references inside an unsafe {} block, compilation still fails with E0502:
// ❌ does not compile
fn main() {
unsafe {
let mut x = String::from("still checked");
let r1 = &x;
let r2 = &mut x;
println!("{}, {}", r1, r2);
}
}
error[E0502]: cannot borrow `x` as mutable because it is also borrowed as immutable
--> src/main.rs:6:18
|
5 | let r1 = &x;
| -- immutable borrow occurs here
6 | let r2 = &mut x;
| ^^^^^^ mutable borrow occurs here
7 | println!("{}, {}", r1, r2);
| -- immutable borrow later used here
Comparing Safe Rust and Unsafe Rust Guarantees
| Aspect | Safe Rust | Unsafe Rust |
|---|---|---|
| Undefined Behavior (UB) | Impossible (assuming sound unsafe libraries and compiler) |
Possible if programmer violates safety invariants |
Borrow checker on &T / &mut T |
Enforced | Still enforced |
Raw pointer dereferencing (*ptr) |
Forbidden (E0133) |
Permitted; caller must ensure alignment, initialization, and aliasing |
| Integer overflow / bounds checks | Checked ([] panics on out-of-bounds) |
[] still panics; .get_unchecked() skips bounds check |
| Audit surface | Does not need manual memory-safety auditing | Every unsafe block is part of the memory-safety audit boundary |
fn main() {
let values = [10, 20, 30];
// Safe Rust: bounds-checked indexing (`values[1]`):
let safe_elem = values[1];
// Unsafe Rust: unchecked indexing requires an `unsafe` block and a safety proof:
// SAFETY: index `2` is strictly less than `values.len()` (`3`).
let unsafe_elem = unsafe { *values.get_unchecked(2) };
println!("safe_elem = {}, unsafe_elem = {}", safe_elem, unsafe_elem);
}
safe_elem = 20, unsafe_elem = 30