Rust Unsafe Code
Rust checks memory rules at compile time, and the checks protect most programs from crashes and security flaws. A few jobs need abilities that the compiler cannot verify, such as talking to hardware or calling code written in another language. The unsafe keyword unlocks those abilities. Treat it like a power tool with a safety guard removed: useful in trained hands, and risky without care.
What Unsafe Means
The keyword does not turn off the borrow checker or the type system. An unsafe block grants five extra abilities, and the programmer takes responsibility for keeping memory valid.
SAFE RUST UNSAFE BLOCK
+-------------+ +--------------------------+
| Compiler | | Compiler still checks |
| proves rules| | most rules, yet you must |
| are met | | prove the five special |
+-------------+ | operations are valid |
+--------------------------+
The Five Unsafe Abilities
- Dereference a raw pointer
- Call an unsafe function or method
- Access or change a mutable static variable
- Implement an unsafe trait
- Access fields of a union
Raw Pointers
Raw pointers resemble references without the safety promises. They may point to nothing, they may dangle, and several mutable pointers may point at one value. Two forms exist: *const T for read-only and *mut T for changeable access.
fn main() {
let mut number = 10;
let r1 = &number as *const i32;
let r2 = &mut number as *mut i32;
unsafe {
println!("r1 points to {}", *r1);
*r2 = 20;
println!("r2 now holds {}", *r2);
}
}Creating a raw pointer needs no unsafe block. Reading through it does, because only the programmer knows whether the address is still valid.
number [ 10 ] <---- r1 (*const)
^
+---------- r2 (*mut)
Two pointers, one value: safe Rust forbids this arrangement.
Unsafe Functions
An unsafe function declares that callers must follow rules the compiler cannot check. The documentation should list those rules in a section titled Safety.
/// # Safety
/// The index must be less than the slice length.
unsafe fn get_unchecked_value(items: &[i32], index: usize) -> i32 {
unsafe { *items.get_unchecked(index) }
}
fn main() {
let data = [5, 10, 15];
let value = unsafe { get_unchecked_value(&data, 1) };
println!("{value}");
}
10
The standard method get_unchecked skips the bounds check. Passing a bad index would read random memory, which explains why the call sits inside an unsafe block.
Safe Wrappers Around Unsafe Code
Good Rust programs confine unsafe code to a tiny area and expose a safe function to everyone else. The standard library follows this pattern. The function split_at_mut divides a slice into two mutable halves, which safe code alone cannot express. Inside, it uses raw pointers, and outside, it offers a checked, safe interface.
fn main() {
let mut values = [1, 2, 3, 4, 5, 6];
let (left, right) = values.split_at_mut(3);
left[0] = 100;
right[0] = 400;
println!("{:?}", values);
}
[100, 2, 3, 400, 5, 6]
callers --> [ safe function ] --> [ small unsafe core ]
checks inputs raw pointer work
Calling Code from Other Languages
Rust talks to libraries written in C through the foreign function interface. The compiler cannot inspect foreign code, so every call needs an unsafe block. Many crates, such as libc, wrap these calls in safe functions for everyday use.
Mutable Statics
A global variable that changes invites data races between threads, because nothing guards it. Rust treats access as unsafe. Prefer atomic types, a Mutex, or a OnceLock for global state.
Rules of Good Practice
- Use safe Rust first. Reach for unsafe only when no safe route exists.
- Keep each unsafe block as small as possible.
- Write a comment above each block explaining why it is valid.
- Wrap the block inside a function that exposes a safe interface.
- Test the surrounding code thoroughly, and run Miri, a tool that detects undefined behavior.
What Can Go Wrong
| Mistake | Effect |
|---|---|
| Dereferencing a dangling pointer | Reads garbage or crashes |
| Writing past the end of memory | Corrupts other data |
| Creating two mutable references to one value | Undefined behavior |
| Using memory after freeing it | Unpredictable results |
Undefined behavior means the language makes no promises about what happens. The program may appear to work for months and fail later.
Auditing Unsafe Code
Search a project for the word unsafe to list every spot that needs human review. A small count signals a project that leans on the compiler for safety.
Key Points
unsafegrants five extra abilities and shifts responsibility to the programmer.- Raw pointers can be created freely and dereferenced only inside unsafe blocks.
- Wrap unsafe code in safe functions with clear documentation.
- Prefer safe alternatives such as
Mutexand atomics.
