Mark all code that may violate safety guarantees with a visible unsafe keyword
[RUST-REF-UNSAFE-KEYWORD].
The following constructs require explicit unsafe visibility:
extern blocks must be declared as unsafe extern
The #[no_mangle] attribute must be written as #[unsafe(no_mangle)]
The #[export_name] attribute must be written as #[unsafe(export_name)]
The #[link_section] attribute must be written as #[unsafe(link_section)]
Note
Starting with Rust Edition 2024, the use of unsafe is required in these contexts.
The #[link] and #[link_ordinal] attributes are implicitly covered by the
unsafe extern requirement, as they must appear on extern blocks.
See rust-lang/rust#82499 for the tracking issue on unsafe attributes.
|
|
|
|
Auditability and review
unsafe blocks create clear audit boundaries where reviewers can focus on code
that may violate Rust’s safety guarantees [RUSTNOMICON-MEET-SAFE]
Safety-critical standards like ISO 26262 [ISO-26262] and
DO-178C [DO-178C] require traceability of hazardous operations
Helps enumerate and document all places where safety requirements must be manually upheld
Satisfies ISO 26262 Part 6, Table 1, objective 1c (use of language subsets)
Satisfies DO-178C Section 6.3.4.f (source code traceability)
Explicit acknowledgment of responsibility
The unsafe keyword signals that the programmer is taking responsibility for
upholding invariants the compiler cannot verify
Prevents accidental use of unsafe operations without conscious decision
Aligns with the principle of defense in depth in safety-critical systems
Static analysis and tooling
Tools like cargo-geiger [CARGO-GEIGER], unsafe-inspect,
and custom linters can automatically locate and count unsafe blocks
Enables metrics like “unsafe density” for safety assessments
Supports qualification evidence required by certification standards
Traceability for certification
Safety-critical certifications require demonstrating that hazardous operations are
identified and controlled
Visible unsafe tokens provide direct linkage to safety cases and hazard analyses
Facilitates the required documentation that each unsafe operation has been reviewed
and justified
|
|
|
|
|
The #[no_mangle] attribute is unsafe because it can be used to declare a function
identifier that conflicts with an existing symbol. This noncompliant example declares an
unmangled function named convert that is missing the required unsafe wrapper in Rust 2024.
This noncompliant example requires Rust Edition 2021 or earlier to compile.
In Rust Edition 2024, missing unsafe wrappers cause compilation errors.
compile fail
// Undefined behavior by the linker or loader is possible
// if another 'convert' function is defined.
#[no_mangle]
fn convert() {}
fn main() {
convert();
}
|
|
|
|
|
Rust Edition 2024 enforces that the no_mangle attribute requires an unsafe keyword,
as shown in this compliant example.
NOTE: This code can still have undefined behavior if the convert function symbol is
defined more than once.
#[unsafe(no_mangle)] // compliant
fn convert() {}
fn main() {
convert();
}
|
|
|
|
|
This noncompliant example misdeclares the malloc function in an extern "C" block
by specifying the type of the size parameter as f32 instead of usize.
An extern block is unsafe because undefined behavior can occur if types or functions are
misdeclared. This is true even if the declarations are not used. This noncompliant example
requires Rust Edition 2021 or earlier to compile.
compile fail
use std::ffi;
extern "C" {
// If 'malloc' is otherwise defined with a 'usize' argument, the compiler
// may generate code for calls to this function using this incompatible declaration,
// resulting in undefined behavior.
fn malloc(size: f32) -> *mut ffi::c_void;
}
fn main() {}
|
|
|
|
|
Rust Edition 2024 enforces that extern "C" blocks require an unsafe keyword,
as shown in this compliant example.
NOTE: This code can still have undefined behavior if the declared malloc function is
incompatible with the actual definition. To eliminate this undefined behavior, the
declaration for malloc used in this compliant example accepts one argument of type usize.
use std::ffi;
unsafe extern "C" {
// Here the assumption is that malloc is the one defined by C's stdlib.h
// and that size_of::<usize>() == size_of::<size_t>()
fn malloc(size: usize) -> *mut ffi::c_void;
fn free(ptr: *mut ffi::c_void);
}
fn main() {
unsafe {
let ptr = malloc(1024);
if !ptr.is_null() {
free(ptr);
}
}
}
|
|
|
|
|
The #[export_name] and #[link_section] attributes can cause undefined behavior if
misused, as they affect symbol resolution and memory layout at link time. Without the
unsafe keyword, these hazards are not visible to reviewers or tools.
This noncompliant example has two separate problems. First, it uses an #[export_name]
attribute without an unsafe wrapper. This attribute controls the symbol name used during
linking. If another symbol with the same name exists, it causes undefined behavior.
Rust 2024 requires it to be marked unsafe.
The second problem is that this noncompliant example uses a #[link_section] attribute
without an unsafe wrapper. This attribute places the item in a specific linker section.
Incorrect section placement can cause undefined behavior (e.g., placing mutable data in
read-only sections, or interfering with special sections like .init).
This noncompliant example requires Rust Edition 2021 or earlier to compile.
compile fail
// Collides with the C library 'printf' function
#[export_name = "printf"] // noncompliant
// Missing unsafe marker - noncompliant in Rust 2024
#[link_section = ".init_array"] // noncompliant
static DATA: u32 = 42; // Corrupts initialization table!
fn main() {
println!("DATA = {DATA}");
}
|
|
|
|
|
The #[export_name] and #[link_section] attributes must use the unsafe() wrapper
to make their safety implications visible.
// SAFETY: 'custom_symbol' does not conflict with any other symbol
#[unsafe(export_name = "custom_symbol")]
pub fn my_function() {}
// SAFETY: Placing data in a specific section for embedded systems
#[unsafe(link_section = ".noinit")]
static mut PERSISTENT_DATA: [u8; 256] = [0; 256];
// SAFETY: Custom section for shared memory
#[unsafe(link_section = ".shared")]
static SHARED_BUFFER: [u8; 4096] = [0; 4096];
fn main() {
my_function();
println!("shared buffer size = {}", SHARED_BUFFER.len());
unsafe { let _ = PERSISTENT_DATA[0]; }
}
|
Enforcement
This guideline can be enforced through the following mechanisms:
Rust Edition 2024: Migrating to Rust Edition 2024 makes violations of this guideline
compilation errors for extern blocks and unsafe attributes.
Compiler Lints: Enable the following lints:
#![deny(unsafe_code)] - Denies all unsafe code (use #[allow(unsafe_code)] for justified exceptions)
#![deny(unsafe_op_in_unsafe_fn)] - Requires explicit unsafe blocks within unsafe functions
#![warn(unsafe_attr_outside_unsafe)] - Warns about unsafe attributes without the unsafe() wrapper (pre-2024)
Static Analysis Tools:
cargo-geiger - Counts and reports unsafe code usage
cargo-audit - Checks for known vulnerabilities in dependencies
Custom Clippy lints for project-specific requirements
Code Review: Manual review of all code containing unsafe tokens should be
part of the development process, with documented justification for each usage.
Related guidelines
* Minimize the scope of unsafe blocks
* Document safety invariants for all unsafe code with // SAFETY: comments
* Prefer safe abstractions over raw unsafe code
* Use #![forbid(unsafe_code)] at crate level where possible, with explicit exceptions
|