🎓 A1Learner
🔍

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?