Unsafe Rust

Calling Unsafe Functions

Calling an unsafe function or method—whether defined in Rust with unsafe fn or imported from a foreign language inside an unsafe extern "C" block—requires an unsafe { ... } block at the call site. The unsafe block signifies that the caller has read the callee's # Safety documentation and verified every precondition before invoking the function.

Verifying Preconditions Before the unsafe Block

All runtime checks, bounds assertions, and validation logic belong in safe Rust outside the unsafe block, leaving only the verified call inside unsafe { ... }:

/// Extracts a sub-slice without bounds checks.
///
/// # Safety
/// `start <= end` and `end <= slice.len()` must hold.
unsafe fn subslice_unchecked(slice: &[u8], start: usize, end: usize) -> &[u8] {
    // SAFETY: Caller guarantees `start <= end <= slice.len()`.
    unsafe {
        let ptr = slice.as_ptr().add(start);
        std::slice::from_raw_parts(ptr, end - start)
    }
}

fn parse_frame(frame: &[u8]) -> Option<(&[u8], &[u8])> {
    if frame.len() < 4 {
        return None;
    }

    // SAFETY: We checked `frame.len() >= 4` above, so `0 <= 4 <= frame.len()`
    // and `4 <= frame.len() <= frame.len()` both hold.
    unsafe {
        let header = subslice_unchecked(frame, 0, 4);
        let payload = subslice_unchecked(frame, 4, frame.len());
        Some((header, payload))
    }
}

fn main() {
    let raw = [0x01, 0x02, 0x03, 0x04, 0xAA, 0xBB];
    println!("parsed: {:?}", parse_frame(&raw));
}
parsed: Some(([1, 2, 3, 4], [170, 187]))

Calling Foreign C Functions (unsafe extern "C")

Foreign functions compiled by C, C++, or assembly compilers are not checked by rustc. In the Rust 2024 edition, extern blocks must be prefixed with unsafe extern "C", and calling any foreign function inside requires an unsafe block unless the foreign declaration is explicitly marked safe fn:

use std::ffi::{CStr, c_char};

unsafe extern "C" {
    fn strlen(s: *const c_char) -> usize;
}

fn c_string_length(s: &CStr) -> usize {
    // SAFETY: `CStr::as_ptr()` is guaranteed to return a valid, non-null,
    // NUL-terminated C string pointer that lives for the duration of `strlen`.
    unsafe { strlen(s.as_ptr()) }
}

fn main() {
    let greeting: &CStr = c"Hello from C ABI!";
    println!("strlen({:?}) = {}", greeting, c_string_length(greeting));
}
strlen("Hello from C ABI!") = 17

Common Standard Library unsafe fn Contracts

Standard Library unsafe fn Mandatory Caller Preconditions
slice::from_raw_parts(data, len) data must be non-null and aligned even if len == 0; len * size_of::<T>() bytes must be initialized, in one allocation, $\le \text{isize::MAX}$, and unmutated for 'a
String::from_utf8_unchecked(bytes) bytes must be valid UTF-8 sequences
str::from_utf8_unchecked(v) v must be valid UTF-8 bytes
Vec::from_raw_parts(ptr, len, cap) ptr must have been allocated by the global allocator with the exact alignment of T, len <= cap, and the first len elements initialized
Vec::set_len(new_len) new_len <= capacity(), and elements old_len..new_len must already be initialized
std::mem::transmute::<Src, Dst>(val) size_of::<Src>() == size_of::<Dst>(), and the bit pattern of val must be a valid inhabitant of Dst
MaybeUninit::assume_init(self) The wrapped value must be fully initialized according to T's validity invariant