25.2 Unsafe Blocks and Unsafe Functions

Rust only permits unsafe operations within blocks or functions explicitly marked with the unsafe keyword.

25.2.1 Declaring an Unsafe Block

An unsafe block is a code block prefixed with unsafe, intended for operations the compiler cannot verify as safe.

A key use of an unsafe block is dereferencing raw pointers. Raw pointers in Rust are similar to C pointers and are discussed in detail in the next section. Creating a raw pointer is safe, but dereferencing it is unsafe, because the compiler cannot ensure the pointer is valid. The unsafe { ... } block explicitly indicates that the programmer is taking responsibility for memory safety.

In the example below, we define a mutable raw pointer using *mut. Dereferencing it is allowed only inside an unsafe block:

fn main() {
    let mut num: i32 = 42;
    let r: *mut i32 = &mut num; // Create a raw mutable pointer to num

    unsafe {
        *r = 99; // Dereference and modify the value using the raw pointer
        println!("The value of num is: {}", *r);
    }
}

Explanation:

  • We create a raw mutable pointer r that points to num.
  • Inside an unsafe block, we dereference r and modify the value.

Although this example is safe in practice, that's because r comes from a valid reference and remains in the same function where it was created.

25.2.2 Declaring an Unsafe Function

You can mark a function with unsafe if its correct usage depends on the caller upholding certain invariants that Rust cannot verify. Within an unsafe function, both safe and unsafe code can be used freely, but any call to such a function must occur in an unsafe block.

unsafe fn dangerous_function(ptr: *const i32) -> i32 {
    // Dereferencing a raw pointer is allowed here.
    *ptr
}

fn main() {
    let x = 42;
    let ptr = &x as *const i32;
    // Any call to an unsafe function must be wrapped in an unsafe block.
    unsafe {
        println!("Value: {}", dangerous_function(ptr));
    }
}

Here, unsafe indicates that this function has certain requirements the caller must satisfy, e.g. passing only valid pointers to i32 instances. Calling it inside an unsafe block implies you've read the function's documentation and will ensure all invariants are upheld.

25.2.3 Unsafe Block or Unsafe Function?

When choosing between using an unsafe block or marking a function as unsafe, focus on the function's contract more than whether it includes unsafe code:

  • Use unsafe fn if misuse (while still compiling) could cause undefined behavior. In other words, the function inherently requires the caller to uphold a particular safety contract.
  • Keep the function safe if no well-typed call can result in undefined behavior. Even if the function body contains an unsafe block, that block may internally uphold all the necessary guarantees.

Avoid marking a function as unsafe solely because it contains an unsafe block—doing so might mislead callers into believing there are extra safety concerns. In general, opt for an unsafe block unless you truly need an unsafe function contract.

A common approach is to encapsulate unsafe code within a safe function, exposing a safe API and confining unsafe code to a small scope.