Skip to content

Unsafe Rust

The unsafe keyword grants access to five capabilities that the compiler cannot verify:

  1. Dereference raw pointers*const T and *mut T
  2. Call unsafe functionsfn foo() { unsafe { ... } }
  3. Access mutable staticsstatic mut X: i32
  4. Implement unsafe traitsunsafe impl Send for T {}
  5. 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 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);
}

Raw pointers can be created in safe code — only dereferencing them requires unsafe:

let x = 42;
let ptr: *const i32 = &x; // safe
let ptr_mut: *mut i32 = &mut x; // safe
let null: *const i32 = std::ptr::null(); // safe
unsafe {
// *ptr // only dereferencing is unsafe
}
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);
}

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.

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

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.

  • [[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