Unsafe Rust
What unsafe Enables
Section titled “What unsafe Enables”The unsafe keyword grants access to five capabilities that the compiler cannot verify:
- Dereference raw pointers,
*const Tand*mut T - Call unsafe functions,
fn foo() { unsafe { ... } } - Access mutable statics,
static mut X: i32 - Implement unsafe traits,
unsafe impl Send for T {} - Access union fields. Unions require unsafe for field access
unsafe does not disable the borrow checker. It does not bypass Rust’s safety guarantees, it Allows you to do things that the compiler cannot prove are safe. You are responsible for maintaining all invariants manually.
Raw Pointers
Section titled “Raw Pointers”*const T and *mut T
Section titled “*const T and *mut T”Raw pointers are like C pointers, they can be null, dangling, misaligned, or aliased. The compiler does not check them:
let x = 42;let raw_const: *const i32 = &x;let mut y = 42;let raw_mut: *mut i32 = &mut y;
unsafe { println!("const: {}", *raw_const); println!("mut: {}", *raw_mut); *raw_mut = 43; assert_eq!(*raw_mut, 43);}Creating Raw Pointers
Section titled “Creating Raw Pointers”Raw pointers can be created in safe code, only dereferencing them requires unsafe:
let x = 42;let ptr: *const i32 = &x; // safelet ptr_mut: *mut i32 = &mut x; // safelet null: *const i32 = std::ptr::null(); // safe
unsafe { // *ptr // only dereferencing is unsafe}Raw Pointer Operations
Section titled “Raw Pointer Operations”let mut values = [1i32, 2, 3, 4, 5];let ptr: *mut i32 = values.as_mut_ptr();
unsafe { // Offset, returns pointer to ptr + count let second = ptr.add(1); assert_eq!(*second, 2);
// Read without moving let val = ptr.read(); assert_eq!(val, 1);
// Write ptr.write(100); assert_eq!(values[0], 100);
// Read-add-write in one operation let old = ptr.replace(200); assert_eq!(old, 100); assert_eq!(*ptr, 200);}as_ref and as_mut
Section titled “as_ref and as_mut”Convert raw pointers to optional references:
let x = 42;let ptr: *const i32 = &x;
let reference: Option<&i32> = unsafe { ptr.as_ref() };assert_eq!(reference, Some(&42));
let null: *const i32 = std::ptr::null();let reference: Option<&i32> = unsafe { null.as_ref() };assert_eq!(reference, None);as_ref() returns None for null pointers, preventing undefined behavior from null dereferences.
Pointer Arithmetic
Section titled “Pointer Arithmetic”let mut arr = [10i32, 20, 30, 40, 50];let ptr = arr.as_mut_ptr();
unsafe { for i in 0..arr.len() { *ptr.add(i) *= 2; }}assert_eq!(arr, [20, 40, 60, 80, 100]);Intuition
Section titled “Intuition”Unsafe Rust lets you bypass the borrow checker’s guarantees when you can prove safety manually. Raw pointers, unsafe function calls, and trait implementations require unsafe blocks. FFI (Foreign Function Interface) uses unsafe to call C code. The key insight is that unsafe does not disable the type system; it adds five additional capabilities that the compiler cannot verify automatically. Properly encapsulated unsafe code behind safe abstractions maintains the overall safety guarantee.
Cross-References
Section titled “Cross-References”- [[rust/02-ownership-borrowing/ownership]] - What unsafe bypasses
- [[rust/05-traits-generics/traits-and-generics]] - Unsafe trait implementations
- [[rust/06-concurrency/concurrency]] - Unsafe Send and Sync implementations
- [[rust/07-cargo-ecosystem/cargo-and-ecosystem]] - FFI and system programming patterns