Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Unsafe Code and Macros

Rule

Deny project-written unsafe by default and introduce unsafe or macros only when a documented requirement outweighs their proof and readability costs.

Activation

Apply this page for unsafe, FFI, raw pointers, macros, proc macros, or generated implementations.

Why

Unsafe moves proof obligations outside the compiler, while macros can hide generated behavior and control flow.

Do

  • Keep unsafe_code = "deny" in the default workspace policy.
  • Prefer safe Rust and mature crates.
  • Grant unsafe exceptions at crate scope with a documented policy.
  • Isolate unsafe behind the smallest safe API and keep blocks minimal.
  • Put SAFETY: comments beside every unsafe block or impl.
  • Document public unsafe functions and traits with # Safety.
  • Run Miri where it supports crates containing project-written unsafe.
  • Keep FFI crates thin and translate portable boundary types into safe core logic.
  • Use macros only for substantial mechanical repetition or a clear declarative pattern.
  • Keep macro input syntax narrow, output predictable, and errors understandable.

Avoid

  • Do not use unsafe to bypass borrow checking or optimize without measurement.
  • Do not lower the workspace-wide unsafe policy for one crate.
  • Do not exchange Rust-owned allocations or process-global assumptions across dynamic-library boundaries.
  • Do not use uninitialized memory without a type-specific validity proof.
  • Do not write a macro for one or two call sites or to invent control flow.
  • Do not use a proc macro when ordinary Rust or macro_rules! is clear enough.
  • Do not generate surprising names, impls, modules, or side effects.

Safety Notes

Rust 2024 uses explicit unsafe forms such as unsafe extern and #[unsafe(no_mangle)] where required. Dependency unsafe does not justify local unsafe.

Public API Notes

Public macros and unsafe contracts are public API; keep their accepted syntax and caller obligations small and documented.

Example

#![allow(unused)]
fn main() {
macro_rules! impl_id_display {
    ($type:ty) => {
        impl std::fmt::Display for $type {
            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
                self.as_str().fmt(f)
            }
        }
    };
}
}

Use this only when several ID types already share exactly the same valid behavior.

Exceptions

  • Allow unsafe in FFI, platform, hardware, or measured low-level crates with local policy.
  • Keep an existing unsafe crate’s exception local when removal is out of scope.
  • Use small test macros for readable repetitive case tables.
  • Use generation when it replaces large error-prone handwritten implementations.