Safety Guidelines
Unsafe Needs Reason, Should be Avoided (M-UNSAFE)
You must have a valid reason to use unsafe
. The only valid reasons are
- novel abstractions, e.g., a new smart pointer or allocator,
- performance, e.g., attempting to call
.get_unchecked()
, - FFI and platform calls, e.g., calling into C or the kernel, ...
Unsafe code lowers the guardrails used by the compiler, transferring some of the compiler's responsibilities to the programmer. Correctness of the resulting code relies primarily on catching all mistakes in code review, which is error-prone. Mistakes in unsafe code may introduce high-severity security vulnerabilities.
You must not use ad-hoc unsafe
to
- shorten a performant and safe Rust program, e.g., 'simplify' enum casts via
transmute
, - bypass
Send
and similar bounds, e.g., by doingunsafe impl Send ...
, - bypass lifetime requirements via
transmute
and similar.
Ad-hoc here means unsafe
embedded in otherwise unrelated code. It is of course permissible to create properly designed, sound abstractions doing these things.
In any case, unsafe
must follow the guidelines outlined below.
Novel Abstractions
- Verify there is no established alternative. If there is, prefer that.
- Your abstraction must be minimal and testable.
-
It must be hardened and tested against "adversarial code", esp.
- If they accept closures they must become invalid (e.g., poisoned) if the closure panics
- They must assume any safe trait is misbehaving, esp.
Deref
,Clone
andDrop
.
-
Any use of
unsafe
must be accompanied by plain-text reasoning outlining its safety - It must pass Miri, including adversarial test cases
- It must follow all other unsafe code guidelines
Performance
-
Using
unsafe
for performance reasons should only be done after benchmarking -
Any use of
unsafe
must be accompanied by plain-text reasoning outlining its safety. This applies to both callingunsafe
methods, as well as providing_unchecked
ones. - The code in question must pass Miri
- You must follow the unsafe code guidelines
FFI
-
We recommend you use an established interop library to avoid
unsafe
constructs - You must follow the unsafe code guidelines
- You must document your generated bindings to make it clear which call patterns are permissible
Further Reading
All Code Must be Sound (M-UNSOUND)
Unsound code is seemingly safe code that may produce undefined behavior when called from other safe code, or on its own accord.
Meaning of 'Safe' The terms safe and
unsafe
are technical terms in Rust.A function is safe, if its signature does not mark it
unsafe
. That said, safe functions can still be dangerous (e.g.,delete_database()
), andunsafe
ones are, when properly used, usually quite benign (e.g.,vec.get_unchecked()
).A function is therefore unsound if it appears safe (i.e., it is not marked
unsafe
), but if any of its calling modes would cause undefined behavior. This is to be interpreted in the strictest sense. Even if causing undefined behavior is only a 'remote, theoretical possibility' requiring 'weird code', the function is unsound.Also see Unsafe, Unsound, Undefined.
#![allow(unused)] fn main() { // "Safely" converts types fn unsound_ref<T>(x: &T) -> &u128 { unsafe { std::mem::transmute(x) } } // "Clever trick" to work around missing `Send` bounds. struct AlwaysSend<T>(T); unsafe impl<T> Send for AlwaysSend<T> {} unsafe impl<T> Sync for AlwaysSend<T> {} }
Unsound abstractions are never permissible. If you cannot safely encapsulate something, you must expose unsafe
functions instead, and document proper behavior.
No Exceptions
While you may break most guidelines if you have a good enough reason, there are no exceptions in this case: unsound code is never acceptable.
It's the Module Boundaries Note that soundness boundaries equal module boundaries! It is perfectly fine, in an otherwise safe abstraction, to have safe functions that rely on behavior guaranteed elsewhere in the same module.
#![allow(unused)] fn main() { struct MyDevice(*const u8); impl MyDevice { fn new() -> Self { // Properly initializes instance ... todo!() } fn get(&self) -> u8 { // It is perfectly fine to rely on `self.0` being valid, despite this // function in-and-by itself being unable to validate that. unsafe { *self.0 } } } }
Unsafe Implies Undefined Behavior (M-UNSAFE-IMPLIES-UB)
The marker unsafe
may only be applied to functions and traits if misuse implies the risk of undefined behavior (UB).
It must not be used to mark functions that are dangerous to call for other reasons.
#![allow(unused)] fn main() { // Valid use of unsafe unsafe fn print_string(x: *const String) { } // Invalid use of unsafe unsafe fn delete_database() { } }