Rust Unsafe
An unsafe block is a small, clearly marked area of code. Inside it, a programmer may do five extra things and takes personal responsibility for them.
Power 1: read through a raw pointer
Making the pointer is safe — only reading or writing through it needs the block. The // SAFETY: line states why this read is sound:
let number = 7;
let ptr = &raw const number;
unsafe {
// SAFETY: ptr points at number, a live i32, for this whole block.
println!("ptr reads {}", *ptr);
}Power 2: call an unsafe function
An unsafe fn must be called inside an unsafe block. Forget, and you get error E0133. Unsafe code inside its body needs its own block too:
unsafe fn triple(x: i32) -> i32 { x * 3 }
let answer = unsafe { triple(4) };
println!("answer = {answer}");Power 3: touch a mutable static
A static mut is shared by the whole program, so every read and write is unsafe. The counter is read back through a raw pointer:
static mut COUNTER: u32 = 0;
unsafe {
COUNTER += 1;
COUNTER += 1;
println!("count = {}", *(&raw const COUNTER));
}Power 4: implement an unsafe trait
Some traits carry a promise the compiler cannot check. Marking the trait unsafe — and the impl unsafe — is how you sign that promise:
unsafe trait Marker {}
struct Token;
unsafe impl Marker for Token {}Power 5: read a union field
Rust cannot know which field was written last, so reading any field of a union is unsafe. Here the first byte of the number 1 is read back:
union Bits { whole: u32, bytes: [u8; 4] }
let b = Bits { whole: 1 };
let first = unsafe { b.bytes[0] };
println!("first byte = {first}");One contract to trust completely
slice::from_raw_parts rebuilds a slice from a pointer and a length, trusting both completely. A wrong pointer or length is undefined behaviour — not an error message:
let data = [10, 20, 30];
let view = unsafe { std::slice::from_raw_parts(data.as_ptr(), data.len()) };
println!("view = {:?}", view);Example
fn main() {
let mut value = 10;
let ptr: *mut i32 = &mut value;
unsafe {
// SAFETY: ptr points to a live i32 we own, properly aligned, used once here.
*ptr += 5;
}
println!("value = {}", value);
}Key points
- unsafe opens a block where five extra powers are allowed.
- It turns nothing else off.
- A raw pointer may be null, dangling, or unaligned.
- Inside unsafe, you are the one who must be sure it is valid.
- Keep unsafe small on purpose.
- Less code inside the block means less code a human must re-check by hand.
- The standing advice is simple.
- Minimise the amount of unsafe code in a code base.
- Sound: a wrapper is sound when safe code cannot use it to break the rules.
- An abstraction built on unsafe is sound under one test.
- No safe code, using it as documented, can cause undefined behaviour through it.
- Undefined behaviour means the program may do anything at all.
- It may crash.
- It may corrupt data.
- It may look fine while being wrong.
- Keep the trusted state private.
- The fields your unsafe code relies on should live in one module.
- Only audited code can touch them there.
- Privacy stops unrelated safe code from quietly breaking your invariants.
- The compiler assumes several things at all times.
- References are never null.
- They are never dangling.
- They are correctly aligned.
- A bool is only ever 0 or 1.
- A char is a valid Unicode value.
- Unsafe code must keep every one of these promises, everywhere
- The goal is always a safe outside over a small unsafe inside.
- Vec and String are built exactly this way.
- A little audited unsafe sits at the core.
- A safe API serves everyone else.
In this example, value is an ordinary i32 holding 10. ptr is a raw mutable pointer to it, created safely. Inside the unsafe block, *ptr += 5 writes through the pointer, making value 15. The SAFETY comment states why this is allowed. The pointer points at a live i32 that we own. It is correctly aligned. It is used once here. The println! prints value = 15. Notice how small the unsafe part is — one line, with its proof written above it.
Note
Tools like Miri help audit unsafe code.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
Does unsafe disable the borrow checker?