25.2 Unsafe Blocks and Functions
Operations designated as unsafe can only be performed within contexts explicitly marked by the unsafe
keyword.
25.2.1 Unsafe Blocks
An unsafe { ... }
block isolates a segment of code containing one or more unsafe operations. This is the most common way to introduce unsafety. It signals that the code within the block might perform actions requiring manual safety verification.
A frequent use case is dereferencing raw pointers. While creating, passing, or comparing raw pointers is safe, reading from or writing to the memory they point to (*ptr
) requires an unsafe
block. This is because the compiler cannot guarantee that the pointer is valid (i.e., not null, dangling, properly aligned, or pointing to initialized memory of the correct type).
fn main() { let mut num: i32 = 42; // Creating a raw pointer from a valid reference is safe. let r_ptr: *mut i32 = &mut num; // Dereferencing the raw pointer requires an unsafe block. unsafe { println!("Value before: {}", *r_ptr); // Modify the value through the raw pointer. *r_ptr = 99; println!("Value after: {}", *r_ptr); } // The original variable reflects the change. println!("Final value of num: {}", num); // num is now 99 }
In this example, the operation is safe because r_ptr
originates from a valid mutable reference &mut num
. The unsafe
block serves as an annotation that the programmer, not the compiler, is responsible for ensuring this validity.
25.2.2 Unsafe Functions
A function can be declared as unsafe fn
if calling it requires the caller to satisfy certain preconditions (invariants) that the compiler cannot enforce through the type system or borrow checker alone. Such functions can perform unsafe operations internally without needing additional unsafe
blocks for those specific operations.
However, calling an unsafe fn
is itself an unsafe operation and must occur within an unsafe
block or another unsafe fn
.
// This function is unsafe because dereferencing `ptr` is only valid // if the caller guarantees `ptr` points to valid, initialized memory. unsafe fn read_from_pointer(ptr: *const i32) -> i32 { *ptr // Unsafe operation permitted directly within `unsafe fn`. } fn main() { let x = 42; let ptr = &x as *const i32; // Calling an unsafe function requires an unsafe block. let value = unsafe { read_from_pointer(ptr) }; println!("Value read via unsafe fn: {}", value); }
The unsafe
keyword on the function signature acts as a contract: “Warning: This function relies on preconditions not checked by the compiler. Incorrect usage can lead to undefined behavior. Ensure you meet its documented requirements before calling.”
25.2.3 unsafe fn
vs. unsafe
Block
Choosing between an unsafe fn
and an unsafe
block inside a safe function depends on where the responsibility for safety lies:
- Use
unsafe fn
when the function has preconditions that the caller must fulfill to ensure safety. Violating these preconditions, even if the function call type-checks, could lead to UB. Safety depends on the caller’s context. - Use an
unsafe
block inside a safe function (fn
) when the function itself can guarantee that its internal unsafe operations are performed correctly, provided the function is called with arguments valid according to its safe signature. Safety is maintained by the function’s implementation.
Best Practice: Encapsulate unsafe operations within unsafe
blocks inside safe functions whenever feasible. This minimizes the surface area of unsafety and presents a safe interface to the rest of the codebase. Reserve unsafe fn
for interfaces where safety fundamentally depends on guarantees provided by the caller, often seen in FFI or low-level abstractions.