Course contentsShow
← Rust
Lesson 1391 of 1,91012. Unsafe Rust and Low-Level AccessFree lesson

Reasoning about Unsafe Blocks

You will be able to identify the minimal code surface area that requires the unsafe keyword to satisfy the borrow checker.

In this lesson, you will learn how to identify the absolute minimal surface area required for an unsafe block to satisfy the borrow checker while maintaining system integrity.

When you encounter a situation where the Rust compiler cannot verify memory safety—such as dereferencing a raw pointer or calling a foreign function—you must wrap that specific operation in an unsafe block. A common pitfall for intermediate developers is wrapping too much code inside that block.

The goal is to keep the unsafe block as small as humanly possible. Why? Because the unsafe keyword serves as a signal to yourself and your reviewers: "The compiler cannot help me here; I must guarantee correctness manually." If your block includes safe code (like variable initialization or logic checks), you increase the surface area that requires manual auditing.

Consider this scenario where we access an element from a slice without bounds checking:

fn get_unchecked(slice: &[i32], index: usize) -> i32 {
    // We validate the index safely first
    assert!(index < slice.len());

    // Only the pointer dereference is unsafe
    unsafe {
        *slice.as_ptr().add(index)
    }
}

In this example, the assert! is kept outside the unsafe block. Even though the assertion is a precondition for safety, it is valid Rust code that doesn't need to be part of the "untrusted" zone. By isolating the *slice.as_ptr().add(index) operation, you clearly define exactly which line is responsible for the potential memory hazard.

If you put the entire function inside the unsafe block, you lose the ability to distinguish between what the compiler verified and what you had to manually guarantee. Always keep your safe logic in the "light" and only push the smallest possible pointer arithmetic or FFI call into the unsafe shadow. This makes it significantly easier to audit your code during debugging or security reviews, as you won't have to squint through lines of safe boilerplate to find the actual source of a potential violation.

Key Takeaway: The smaller your unsafe block, the easier it is to prove your invariants and uphold the safety contract.