| name | rust-syntax-unsafe |
| description | Use when the user writes `unsafe fn` or `unsafe { }` blocks, dereferences raw pointers, calls FFI, uses `transmute`, deals with `MaybeUninit`, asks about undefined behavior, runs `miri`, or works with strict-provenance APIs (1.84). Prevents UB via aliasing violations, deref of a dangling pointer, breaking auto-trait invariants, missing `unsafe_op_in_unsafe_fn` warnings in edition 2024, and skipping `// SAFETY:` comments. Covers: the 8 superpowers post-2024 (deref raw ptr, call unsafe fn, access mut static, impl unsafe trait, union field, unsafe extern blocks, target_feature, unsafe attributes), undefined-behavior enumeration verbatim from the Reference, strict-provenance APIs (1.84: `expose_provenance`, `with_addr`, `map_addr`, `addr_of!`, `addr_of_mut!`), `unsafe_op_in_unsafe_fn` warn-by-default in 2024, soundness contracts, raw pointers vs references, `transmute`, `MaybeUninit`, `UnsafeCell`, miri verification. Keywords: unsafe, "unsafe fn", "unsafe block", "raw pointer", "*const", "*mut", "deref pointer", "undefined behavior", UB, transmute, "MaybeUninit", "UnsafeCell", "strict provenance", "expose_provenance", "with_addr", "map_addr", "addr_of", "addr_of_mut", "unsafe_op_in_unsafe_fn", "SAFETY comment", miri, "// SAFETY:", "8 superpowers", "5 superpowers", "unsafe extern", "FFI safety", "what is unsafe for", "what can unsafe do", "is this UB", "why miri error", "how to avoid UB".
|
| license | MIT |
| compatibility | Designed for Claude Code. Requires Rust 1.85+, edition 2024. |
| metadata | {"author":"OpenAEC-Foundation","version":"1.0"} |
rust-syntax-unsafe
unsafe in Rust does NOT disable the borrow checker, type checker, or any safety analysis. It opts a region of code into eight additional capabilities that the compiler cannot prove sound on its own, and it transfers the proof obligation to the human author. Every unsafe block represents a soundness contract: the author asserts that the invariants the compiler cannot verify hold at this site. Violating that contract is undefined behavior (UB), and UB poisons the entire program, including the safe code that called into it.
This skill is the authoritative entry point for the unsafe surface as of Rust 1.85 / edition 2024. ALWAYS treat the Reference unsafety and behavior-considered-undefined pages as ground truth; NEVER rely on the older "5 superpowers" wording from The Book (it predates edition 2024 and the target_feature formalization).
Quick Reference
Core rules (deterministic)
- ALWAYS write a
// SAFETY: comment on the line immediately preceding every unsafe { ... } block and every #[unsafe(...)] attribute. The comment MUST enumerate the invariants the caller relies on. NEVER omit it, even when the invariant is obvious to you.
- NEVER call an
unsafe fn from inside another unsafe fn body without its own inner unsafe { ... } block. The unsafe_op_in_unsafe_fn lint is #[warn]-by-default in edition 2024 and #[deny]-by-default in many production lint configs.
- NEVER write
extern "C" { ... } in edition 2024 source. ALWAYS write unsafe extern "C" { ... }. Each item inside MAY be opted into safe per item; the block itself MUST be unsafe.
- NEVER use bare
#[no_mangle], #[link_section = "..."], #[export_name = "..."], or #[used] in edition 2024. ALWAYS wrap them: #[unsafe(no_mangle)], #[unsafe(link_section = "...")], #[unsafe(export_name = "...")], #[unsafe(used)].
- NEVER cast a pointer to integer and back with plain
as usize and as *const T in code that must be portable to strict-provenance targets. ALWAYS use ptr.expose_provenance() + core::ptr::with_exposed_provenance(addr) when you need an int round-trip, or ptr.with_addr(addr) / ptr.map_addr(|a| ...) when you keep the same provenance.
- NEVER take a reference (
& or &mut) to a static mut. ALWAYS use &raw const STATIC / &raw mut STATIC (stable native syntax since 1.82) or core::ptr::addr_of! / core::ptr::addr_of_mut!. The static_mut_refs lint is deny-by-default in 2024.
- NEVER
transmute<A, B> when size_of::<A>() != size_of::<B>(). The compiler rejects this at compile time, but the more common silent footgun is transmute between two same-sized types with different validity invariants (e.g. u8 to bool, u32 to char, usize to a reference). ALWAYS prefer as for numeric primitives, From/Into for ZST/wrapper conversions, and MaybeUninit::assume_init for uninit memory.
- NEVER dereference a
*const T or *mut T without proving four properties at the site: (a) non-null, (b) properly aligned for T, (c) points into a single live allocation of at least size_of::<T>() bytes, (d) the bytes form a valid value of T. The // SAFETY: comment MUST address all four.
- NEVER hold a
&mut T and dereference a *mut T that aliases the same memory inside the same unsafe block. This is breaking pointer aliasing rules (UB category 4) regardless of whether the program "appears to work".
- ALWAYS run
cargo +nightly miri test on every crate that contains unsafe code, in CI. Miri catches most aliasing, provenance, uninit-read, and OOB-access violations at the MIR level. Miri-clean is the minimum bar for shipping unsafe; the maximum bar adds property-based testing of the public safe API.
Decision tree: "do I need unsafe?"
Does the operation require ANY of the 8 superpowers below?
NO -> The compiler refuses. The code is invalid as written.
Refactor in safe Rust. STOP. Do NOT add unsafe.
YES -> Can I express the same operation through an existing safe API?
YES -> Use the safe API. Examples: AtomicX instead of static mut,
Vec::split_at_mut instead of two raw pointer arithmetic,
MaybeUninit::write instead of transmute-from-zeroed.
NO -> Can I encapsulate the unsafe op behind a SAFE function whose
preconditions are checked by the type system?
YES -> Build the safe wrapper. Add `// SAFETY:` to every
internal unsafe block. Export ONLY the safe function.
NO -> The unsafe contract is part of the PUBLIC API.
Mark the function `unsafe fn`. Document `# Safety`.
Enumerate every precondition. Add property tests + miri.
The 8 superpowers (post-edition-2024 enumeration)
The Reference unsafety page enumerates these as the operations that require an unsafe { } block or unsafe fn context. Each row links to its driving UB category in references/methods.md.
| # | Superpower | Edition 2024 form | Driving UB risk |
|---|
| 1 | Dereference a raw pointer | unsafe { *ptr } | Dangling / misaligned / wrong-prov |
| 2 | Call an unsafe fn | unsafe { f() } | Inherited contract |
| 3 | Read / write a mutable (or unsafe external) static | unsafe { COUNTER += 1 } | Data race |
| 4 | Implement an unsafe trait | unsafe impl Send for T { } | Auto-trait soundness |
| 5 | Access a union field (read or non-assign write) | unsafe { u.field } | Invalid value |
| 6 | Declare items inside unsafe extern { ... } | unsafe extern "C" { fn f(); } | ABI / signature mismatch |
| 7 | Call a #[target_feature]-gated function | unsafe { _mm_add_ps(a, b) } | Illegal instruction |
| 8 | Apply an unsafe attribute to an item | #[unsafe(no_mangle)] | Symbol-name aliasing |
Items 6 and 8 became unsafe-marked in edition 2024 (previously implicit). Item 7 has been carved out since 1.27 but is formalized in the current Reference. The historical "5 superpowers" wording from earlier editions of The Book does NOT cover items 6, 7, or 8 and MUST NOT be quoted in 2024 code reviews.
Undefined Behavior (UB) enumeration
The Reference behavior-considered-undefined page is the ONLY normative list. UB inside unsafe code corrupts the entire program. The categories, verbatim from the Reference (paraphrased only for brevity, full text in references/methods.md):
- Data races. Two threads access the same non-atomic location, at least one writes, no happens-before relation.
- Dangling or misaligned pointer access. "Dangling" means not all bytes are part of the same live allocation, including allocations of size zero pointing to past-the-end.
- Out-of-bounds place projection. Indexing past the end of an array even without dereferencing.
- Breaking pointer aliasing rules.
&T may not be mutated through any path (except UnsafeCell); &mut T may not be read or written by any pointer not derived from it while the reference lives.
- Mutating immutable bytes. Bytes reachable via
&T (and transitively through Box, etc.) are immutable.
- Invoking UB via compiler intrinsics. Misusing
core::intrinsics::* or core::hint::unreachable_unchecked.
- Executing code compiled with platform features the current platform does not support. The
target_feature superpower.
- Calling a function with the wrong call ABI or unwinding past a frame that disallows it (e.g. unwinding through
extern "C" without -C panic=unwind semantics).
- Producing an invalid value. Examples:
bool outside {0, 1}, null fn pointer, surrogate char, ! ever produced, uninit ints/floats/raw pointers, enum discriminant out of range, null/unaligned/dangling references or Box<T>.
- Incorrect inline assembly use. Violating
asm! register constraints, clobbering preserved registers without listing them.
- Violating Rust runtime assumptions.
longjmp skipping destructors, exiting via exit(3) while threads hold non-mem locks the kernel cares about, etc.
- Const-context provenance violations. Producing a non-comparable pointer in a
const evaluation.
CRITICAL clarification from the Reference: "Rust code is incorrect if it exhibits any of the behaviors in the following list. This includes code within unsafe blocks and unsafe functions. unsafe only means that avoiding undefined behavior is on the programmer." See references/methods.md for the per-category contract language.
Strict provenance APIs (Rust 1.84+, stable)
| API | Signature | Use case |
|---|
<*const T>::addr(self) | -> usize | Inspect the integer address WITHOUT exposing provenance. Pure observation. |
<*mut T>::addr(self) | -> usize | Same, on *mut T. |
<*const T>::expose_provenance(self) | -> usize | Inspect integer AND publish provenance into the global pool. Required before round-trip via with_exposed_provenance. |
<*mut T>::expose_provenance(self) | -> usize | Same, on *mut T. |
core::ptr::with_exposed_provenance::<T>(addr: usize) | -> *const T | Materialize a pointer with provenance previously exposed by expose_provenance. Use this for int-to-ptr round-trips. |
core::ptr::with_exposed_provenance_mut::<T>(addr: usize) | -> *mut T | Mutable variant. |
<*const T>::with_addr(self, addr: usize) | -> *const T | Build a new pointer keeping THIS pointer's provenance but adopting a different address. |
<*const T>::map_addr(self, f: impl FnOnce(usize) -> usize) | -> *const T | Same, in closure form. |
core::ptr::addr_of!(place) | -> *const T | Take a raw pointer to a place WITHOUT first creating a reference. Critical for static mut, packed-struct fields, and unaligned memory. |
core::ptr::addr_of_mut!(place) | -> *mut T | Mutable variant. |
&raw const place | -> *const T | Native syntax for addr_of!, stable since 1.82. |
&raw mut place | -> *mut T | Native syntax for addr_of_mut!, stable since 1.82. |
The strict-provenance model treats pointers as (provenance, address) pairs. Plain as usize ptr-to-int casts discard provenance silently and produce non-round-trippable values on hardware with hardware-enforced provenance (e.g. CHERI). ALWAYS use the strict-provenance APIs when you genuinely need an integer view of a pointer.
Patterns
Pattern: writing a sound unsafe block (// SAFETY: discipline)
use core::slice;
pub fn as_bytes<T>(src: &[T]) -> &[u8] {
let len_bytes = core::mem::size_of_val(src);
let ptr = src.as_ptr().cast::<u8>();
unsafe { slice::from_raw_parts(ptr, len_bytes) }
}
EVERY unsafe block has a // SAFETY: paragraph enumerating each invariant the unsafe API requires. The block is the smallest scope that needs unsafe. The wrapper exports only safe behavior.
Pattern: unsafe_op_in_unsafe_fn in edition 2024
pub unsafe fn read_unchecked<T>(slice: &[T], i: usize) -> &T {
unsafe { slice.get_unchecked(i) }
}
This is NOT busywork. The inner unsafe { } exists to mark precisely WHICH operations in the body are unsafe; the outer unsafe fn only marks the function's PUBLIC contract. Pre-2024 conflated these, which hurt review.
Pattern: raw pointers from static mut via strict provenance
static mut COUNTER: u64 = 0;
pub fn bump() -> u64 {
unsafe {
let p = core::ptr::addr_of_mut!(COUNTER);
let cur = p.read();
p.write(cur + 1);
cur + 1
}
}
NEVER write unsafe { let r = &mut COUNTER; *r += 1; }: it forms a &mut to a static mut, which the static_mut_refs lint rejects at deny-by-default. In multi-threaded code, the correct answer is AtomicU64, not raw pointers. See [[rust-impl-concurrency]].
Pattern: MaybeUninit<T> for safe uninitialized memory
use core::mem::MaybeUninit;
pub fn make_array<T: Copy + Default, const N: usize>() -> [T; N] {
let mut uninit: [MaybeUninit<T>; N] =
unsafe { MaybeUninit::uninit().assume_init() };
for slot in &mut uninit {
slot.write(T::default());
}
unsafe { (&uninit as *const _ as *const [T; N]).read() }
}
The dance here is exactly the pattern from the standard library's own internals: the OUTER array is MaybeUninit<T> (so the bit pattern is always valid), each slot is written exactly once, then the whole array is read out. NEVER call MaybeUninit::assume_init on partially-initialized memory.
Pattern: UnsafeCell<T> for sound interior mutability
use core::cell::UnsafeCell;
pub struct MyCell<T> { inner: UnsafeCell<T> }
impl<T> MyCell<T> {
pub fn new(value: T) -> Self { Self { inner: UnsafeCell::new(value) } }
pub fn replace(&self, value: T) -> T {
unsafe { core::mem::replace(&mut *self.inner.get(), value) }
}
}
UnsafeCell<T> is the ONLY way to obtain &mut T from &T soundly in Rust. Every interior-mutability primitive in std (Cell, RefCell, Mutex, RwLock, OnceCell, atomics) wraps UnsafeCell internally. NEVER cast &T to &mut T via transmute or pointer cast; ALWAYS go through UnsafeCell::get.
Pattern: unsafe extern in edition 2024
unsafe extern "C" {
pub safe fn strlen(s: *const core::ffi::c_char) -> usize;
pub unsafe fn memcpy(
dst: *mut core::ffi::c_void,
src: *const core::ffi::c_void,
n: usize,
) -> *mut core::ffi::c_void;
}
See [[rust-impl-ffi-bindgen]] for the broader FFI ownership story (BorrowedFd, OwnedFd, #[repr(C)], opaque pointers). See [[rust-syntax-edition-2024]] for the migration mechanics.
Edge Cases
- Type-state on raw pointers:
*const T is Send and Sync if T: Sync; *mut T is !Send and !Sync. NEVER move raw pointers across threads without explicit unsafe impl Send/unsafe impl Sync for a wrapper newtype, accompanied by // SAFETY: rationale.
& and &mut carry lifetimes; raw pointers do NOT. Converting &'a T to *const T is implicit (as_ptr()); converting back to &'a T requires unsafe { &*ptr } and the responsibility for 'a-respecting validity transfers to you.
core::hint::unreachable_unchecked() is an UB-on-reach intrinsic; the compiler is permitted to assume the call site is unreachable and may delete code paths leading to it. NEVER call it unless you can prove the path is impossible.
core::mem::zeroed::<T>() is UB for many T (any type with a niche that cannot be zero, e.g. &T, Box<T>, NonZeroU32, enums whose discriminant is non-zero). ALWAYS prefer MaybeUninit::zeroed().assume_init() only when you have proved every byte-zero pattern is a valid T.
- Miri runs only via
cargo +nightly miri. Edition 2024 + Miri requires a recent enough nightly; track the Miri page for the supported stable feature surface.
- The Stacked Borrows / Tree Borrows model is what Miri checks: borrowing rules are stricter than the Reference (which is intentionally underspecified). Miri-failure is conclusive evidence of UB; Miri-pass is not proof of soundness (but is the strongest automated check available).
Cross-References
- [[rust-impl-ffi-bindgen]] :
unsafe extern, #[repr(C)], ABI bindings, BorrowedFd/OwnedFd.
- [[rust-syntax-edition-2024]] :
unsafe extern migration, unsafe-attribute syntax, unsafe_op_in_unsafe_fn lint, static_mut_refs migration paths.
- [[rust-core-memory-model]] : aliasing rules,
Send/Sync derivation, interior-mutability category.
- [[rust-syntax-borrowing]] : the safe complement; what
unsafe is allowed to violate that safe Rust isn't.
- [[rust-impl-concurrency]] : atomics as the safe replacement for
static mut raw access.
- [[rust-core-toolchain]] : how to install Miri (
rustup component add miri --toolchain nightly).
Reference Links
Official Sources