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.