SOP: Unsafe Rust ​
Updated Mar 2026Overview ​
Unsafe Rust opts out of some compiler guarantees. It does NOT disable the borrow checker entirely — it unlocks five specific superpowers. This SOP covers when, why, and how to use unsafe correctly.
The Five Superpowers ​
Decision: Do You Need Unsafe? ​
Raw Pointers ​
rust
fn main() {
let mut x = 42;
// Creating raw pointers is safe
let r1 = &x as *const i32; // Immutable raw pointer
let r2 = &mut x as *mut i32; // Mutable raw pointer
// Dereferencing raw pointers requires unsafe
unsafe {
println!("r1 = {}", *r1);
*r2 = 100;
println!("r2 = {}", *r2);
}
}
// Raw pointers can be null (unlike references)
fn might_be_null() {
let p: *const i32 = std::ptr::null();
// unsafe { println!("{}", *p); } // Would segfault!
if !p.is_null() {
unsafe { println!("{}", *p); }
}
}Safe Abstractions Over Unsafe ​
rust
use std::slice;
// split_at_mut in std uses unsafe internally but provides a safe API
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
let len = values.len();
let ptr = values.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
fn main() {
let mut v = vec![1, 2, 3, 4, 5];
let (left, right) = split_at_mut(&mut v, 3);
// Safe API — caller doesn't need unsafe
assert_eq!(left, &[1, 2, 3]);
assert_eq!(right, &[4, 5]);
}Mutable Statics ​
rust
static mut COUNTER: u32 = 0;
fn increment() {
// Any access to mutable static requires unsafe
unsafe {
COUNTER += 1;
}
}
fn get_count() -> u32 {
unsafe { COUNTER }
}
// Better alternative: use atomics or Mutex
use std::sync::atomic::{AtomicU32, Ordering};
static SAFE_COUNTER: AtomicU32 = AtomicU32::new(0);
fn safe_increment() {
SAFE_COUNTER.fetch_add(1, Ordering::SeqCst);
}Implementing Unsafe Traits ​
rust
// Send and Sync are unsafe traits — implementing them is a contract
// that YOU guarantee thread safety
struct MyPointerWrapper {
ptr: *mut u8,
}
// SAFETY: MyPointerWrapper only accesses ptr behind a mutex,
// so it's safe to send between threads.
unsafe impl Send for MyPointerWrapper {}
// SAFETY: All methods that access ptr use synchronization,
// so it's safe to share references between threads.
unsafe impl Sync for MyPointerWrapper {}Calling Unsafe Functions ​
rust
// Unsafe function — caller must uphold invariants
unsafe fn dangerous() {
// Internal operations that require manual safety guarantees
}
fn main() {
unsafe {
dangerous();
}
}
// External functions (FFI) are always unsafe
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("abs(-3) = {}", abs(-3));
}
}Best Practices ​
| Do | Do NOT |
|---|---|
| Minimize unsafe blocks — smallest possible scope | Put entire functions in unsafe blocks |
| Document SAFETY invariants with comments | Leave unsafe uncommented |
| Wrap unsafe in safe public APIs | Expose raw unsafe to callers |
Use assert! and bounds checks before unsafe | Trust input without validation |
| Use standard safe alternatives when they exist | Write unsafe for already-safe operations |
| Test unsafe code extensively (including Miri) | Assume unsafe code is correct because it compiles |
Safety Comments Convention ​
rust
fn get_unchecked(slice: &[i32], index: usize) -> i32 {
// SAFETY: Caller guarantees index < slice.len().
// We've verified this with the assert below.
assert!(index < slice.len());
unsafe { *slice.get_unchecked(index) }
}Testing Unsafe Code with Miri ​
bash
# Install Miri
rustup +nightly component add miri
# Run tests under Miri (detects UB)
cargo +nightly miri test
# Run a specific binary under Miri
cargo +nightly miri runMiri detects:
- Use of uninitialized memory
- Out-of-bounds memory access
- Use-after-free
- Invalid pointer alignment
- Data races (with
-Zmiri-disable-stacked-borrowsflag) - Violations of Stacked Borrows model
Checklist ​
- [ ] Confirm safe Rust cannot accomplish the task
- [ ] Identify which of the five superpowers you need
- [ ] Write the unsafe block as small as possible
- [ ] Add
// SAFETY:comment explaining invariants - [ ] Wrap in a safe public API whenever possible
- [ ] Validate all inputs before the unsafe block
- [ ] Test with
cargo +nightly miri test - [ ] Review with another developer if possible