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 functions, fn foo() { unsafe { ... } }
  3. Access mutable statics, static mut X: i32
  4. Implement unsafe traits, unsafe 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]);

Unsafe Rust is the escape hatch from the borrow checker’s safety guarantees. Think of it as signing a contract: the compiler says “I cannot verify this is safe, so you are responsible for保证ing it.” You get five superpowers: dereferencing raw pointers, calling unsafe functions, accessing mutable statics, implementing unsafe traits, and accessing union fields. The key insight is that unsafe does not disable the borrow checker; it directly lets you do things the checker cannot prove are safe. Most Rust code is safe; unsafe is reserved for FFI, low-level optimisations, and building safe abstractions.