The unsafe Keyword
The unsafe keyword serves two mirror-image roles in Rust:
- 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. - 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