Unsafe Rust
Bypass Rust’s compile-time safety checks for low-level operations, FFI, and performance-critical code.
Unsafe Operations
Five operations require unsafe
blocks:
unsafe { // Dereference raw pointers // Call unsafe functions // Access/modify mutable statics // Implement unsafe traits // Access union fields }
Raw Pointers
let mut num = 5; let r1 = &num as *const i32; let r2 = &mut num as *mut i32; unsafe { println!("r1: {}", *r1); // Dereferencing requires unsafe }
Key differences from references:
- No borrow checking (multiple mutable pointers allowed)
- Can be null or point to invalid memory
- No automatic cleanup
Safe Abstractions
Wrap unsafe code in safe APIs:
fn split_at_mut(values: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) { assert!(mid <= values.len()); unsafe { let ptr = values.as_mut_ptr(); ( slice::from_raw_parts_mut(ptr, mid), slice::from_raw_parts_mut(ptr.add(mid), values.len() - mid), ) } }
FFI (Foreign Function Interface)
extern "C" { fn abs(input: i32) -> i32; } unsafe { println!("Absolute value: {}", abs(-3)); }
Exporting to C:
#[no_mangle] pub extern "C" fn call_from_c() { println!("Called from C!"); }
Static Variables
static mut COUNTER: usize = 0; unsafe { COUNTER += 1; println!("COUNTER: {}", COUNTER); }
Unsafe Traits
unsafe trait Foo { // ... } unsafe impl Foo for i32 { // ... }
Unions
union MyUnion { f1: u32, f2: f32, } let u = MyUnion { f1: 1 }; unsafe { match u { MyUnion { f1: i } => println!("i32: {}", i), } }
Miri Validation
Detect undefined behavior in unsafe code:
cargo +nightly miri run
cargo +nightly miri test
Best Practices
- Minimize scope: Keep unsafe blocks small and focused
- Document invariants: Use
SAFETY:
comments explaining preconditions - Encapsulate: Don’t expose unsafe operations in public APIs
- Test thoroughly: Use Miri and comprehensive test coverage
- Validate inputs: Assert preconditions before unsafe operations
Unsafe Rust enables FFI, zero-cost abstractions, and performance optimizations where safety checks are insufficient.