Unsafe Rust

The unsafe Keyword

The unsafe keyword serves two mirror-image roles in Rust:

  1. On declarations (unsafe fn, unsafe trait, unsafe extern, #[unsafe(...)]): Declares that using or implementing this item has unchecked safety preconditions that the programmer must satisfy to avoid undefined behavior.
  2. On expressions and implementations (unsafe { ... }, unsafe impl): Asserts to the compiler that the programmer has verified and satisfied all safety preconditions of the operations or traits inside.
Dual Roles of the `unsafe` Keyword:

   Declaration Role ("Contract")                Assertion Role ("Fulfillment")
   -----------------------------                ------------------------------
   unsafe fn from_raw(p: *mut T)  <-----------  unsafe { Box::from_raw(p) }
   unsafe trait Send              <-----------  unsafe impl Send for MyPtr {}
   unsafe extern "C" { fn abs(); } <----------  unsafe { abs(-5) }
   #[unsafe(no_mangle)]

All Syntactic Positions of unsafe (Rust 2024 Edition)

In the Rust 2024 edition, unsafe is used across six syntactic constructs:

Syntax Role Meaning
unsafe { ... } Expression block Permits executing unsafe operations inside the block
unsafe fn foo() Function / method signature Declares that calling foo() requires an unsafe block and satisfies safety preconditions
unsafe trait Foo Trait definition Declares that implementing Foo carries safety invariants that cannot be checked by the compiler
unsafe impl Foo for T Trait implementation Asserts that T upholds all safety invariants required by unsafe trait Foo
unsafe extern "C" { ... } Foreign block (required in Rust 2024) Acknowledges that declaring external symbol signatures and types must match the foreign library ABI
#[unsafe(no_mangle)] / #[unsafe(export_name = "...")] Unsafe attribute (required in Rust 2024) Acknowledges that exporting an unmangled linker symbol can collide with global C/OS symbols

Unsafe Traits and unsafe impl

A normal trait guarantees that any type implementing its method signatures is memory-safe. An unsafe trait (such as Send, Sync, or std::alloc::GlobalAlloc) has semantic invariants beyond its method signatures—for example, Send has no methods at all, yet implementing Send for a type containing raw pointers asserts that moving the type across threads cannot cause a data race:

/// # Safety
/// Implementors must guarantee that `as_bytes` returns a slice pointing only
/// to initialized, padding-free bytes of `Self`.
unsafe trait ZeroPaddingBytes {
    fn as_bytes(&self) -> &[u8] {
        // SAFETY: The `unsafe impl` contract guarantees all `size_of::<Self>()`
        // bytes of `self` are initialized and contain no padding.
        unsafe {
            std::slice::from_raw_parts(
                (self as *const Self).cast::<u8>(),
                size_of::<Self>(),
            )
        }
    }
}

#[repr(C)]
struct RgbaPixel {
    r: u8,
    g: u8,
    b: u8,
    a: u8,
}

// SAFETY: `RgbaPixel` is `#[repr(C)]` with four `u8` fields (size 4, align 1)
// and zero padding bytes.
unsafe impl ZeroPaddingBytes for RgbaPixel {}

fn main() {
    let pixel = RgbaPixel {
        r: 255,
        g: 128,
        b: 0,
        a: 255,
    };
    println!("pixel bytes: {:?}", pixel.as_bytes());
}
pixel bytes: [255, 128, 0, 255]

Rust 2024 Edition: unsafe extern and #[unsafe(no_mangle)]

In Rust 2024 edition, declaring an extern block with an inaccurate signature (such as declaring fn free(p: usize) when C expects a valid heap pointer) is recognized as an unsafe declaration that must be written unsafe extern "C" { ... }. Inside an unsafe extern block, individual functions are unsafe to call by default unless explicitly marked safe fn:

unsafe extern "C" {
    // Explicitly marking a stateless mathematical C function as `safe` allows
    // calling it without an `unsafe {}` block at call sites:
    safe fn abs(input: i32) -> i32;
}

fn main() {
    println!("abs(-42) = {}", abs(-42));
}
abs(-42) = 42