Unsafe Rust

Unsafe Functions

An unsafe function (unsafe fn) is a function or method whose correctness depends on preconditions that the Rust type system cannot verify at compile time. Marking a function unsafe fn forces every caller to wrap the call in an unsafe { ... } block and verify that the function's documented safety contract holds for the arguments passed.

When to Mark a Function unsafe fn

A function should be marked unsafe fn if and only if there exists some combination of type-checked arguments or program states for which calling the function causes undefined behavior (memory corruption, dangling references, data races, or invalid bit patterns).

  • Should be unsafe fn: slice::from_raw_parts(ptr, len) (passing a null pointer or out-of-bounds len causes UB), String::from_utf8_unchecked(bytes) (passing non-UTF-8 bytes violates str's validity invariant).
  • Should NOT be unsafe fn: A function that deletes a file, panics on invalid input, leaks memory, or causes a logic bug without violating memory safety.

Rust 2024 Edition Rule: unsafe_op_in_unsafe_fn

In Rust 2021 and earlier editions, the entire body of an unsafe fn implicitly acted as one large unsafe {} block. Starting in the Rust 2024 edition, the unsafe_op_in_unsafe_fn lint is enabled by default: an unsafe fn signature declares a contract to callers, but performing an unsafe operation inside the body still requires an explicit unsafe { ... } block.

This separation prevents large unsafe fn bodies from accidentally invoking unintended unsafe operations without review:

// ❌ does not compile
// In Rust 2024 edition, dereferencing `ptr` inside `unsafe fn` without an
// inner `unsafe { ... }` block is rejected:
unsafe fn read_offset(ptr: *const u32, offset: usize) -> u32 {
    *ptr.add(offset)
}

fn main() {}
error[E0133]: call to unsafe function `std::ptr::const_ptr::<impl *const T>::add` is unsafe and requires unsafe block
 --> src/main.rs:5:6
  |
5 |     *ptr.add(offset)
  |      ^^^^^^^^^^^^^^^ call to unsafe function
  |
  = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/unsafe-op-in-unsafe-fn.html>
note: an unsafe function restricts its caller, but its body is safe by default
 --> src/main.rs:4:1
  |
4 | unsafe fn read_offset(ptr: *const u32, offset: usize) -> u32 {
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Wrapping only the specific pointer arithmetic and dereference inside an inner unsafe { ... } block makes the exact unsafe operation explicit:

/// Returns the element at `offset` from `ptr`.
///
/// # Safety
/// - `ptr` must be non-null, properly aligned, and valid for reads.
/// - `ptr.add(offset)` must point to an initialized `u32` within the same allocation.
unsafe fn read_offset(ptr: *const u32, offset: usize) -> u32 {
    // SAFETY: Caller guarantees `ptr.add(offset)` is in-bounds, aligned, and initialized.
    unsafe { *ptr.add(offset) }
}

fn main() {
    let buffer: [u32; 4] = [100, 200, 300, 400];

    // SAFETY: `buffer.as_ptr()` is valid for 4 elements and offset `2` is in bounds.
    let third = unsafe { read_offset(buffer.as_ptr(), 2) };
    println!("third element: {}", third);
}
third element: 300

Safe Wrapper vs. _unchecked Companion Naming Convention

In the standard library and idiomatic Rust crates, when both a checked safe function and an unchecked unsafe fn exist for the same operation, the unsafe variant is suffixed with _unchecked:

Safe Function (Checked) Unsafe Function (Unchecked Precondition)
slice.get(index) -> Option<&T> unsafe fn get_unchecked(index) -> &T
String::from_utf8(vec) -> Result<String, ...> unsafe fn from_utf8_unchecked(vec) -> String
NonZeroU32::new(n) -> Option<NonZeroU32> unsafe fn new_unchecked(n) -> NonZeroU32
char::from_u32(i) -> Option<char> unsafe fn from_u32_unchecked(i) -> char
u32::checked_add(a, b) -> Option<u32> unsafe fn unchecked_add(a, b) -> u32