Unsafe
Rust's safety guarantees can be limiting when writing low-level code, because they prevent you from doing potentially dangerous things. Unsafe Rust allows you to sidestep some of these restrictions.
Unsafe Rust is very dangerous and can cause undefined behavior (UB). Because of this, it is always marked with the unsafe
keyword. This makes it clear which parts of the code must be carefully vetted for safety. Furthermore, many Rust programs don't use unsafe
at all. It is good practice to always add a comment to unsafe
code, which explains why the code is safe, or what is required to use it safely.
Note that unsafe
code is not allowed to violate Rust's safety guarantees. If unsafe
is used, it's the programmer's responsibility to prevent undefined behavior, although the compiler can't verify this.
Unsafe operations
Using unsafe
, it is possible to perform the following operations, which are not allowed elsewhere: [1][2]
- Dereference
raw pointers
- Call
unsafe
functions (including C functions, compiler intrinsics, and the raw allocator) - Access or modify a mutable static variable
- Access fields of unions
- Implement
unsafe
traits
Unsafe contexts
An unsafe context is a context where the unsafe operations are allowed:
unsafe { }
blocks within a function introduce a scope with an unsafe context.unsafe
functions currently introduce an unsafe context as well, but this will be changed in the future.[3]unsafe
traits are implemented by writingunsafe impl
.
Implications for safe code
When unsafe
code is used incorrectly, it can cause UB; we call such code unsound. Unfortunately, even safe code can be unsound, if it somehow relies on an unsound API. This means that incorrect usage of unsafe
can cause problems in different parts of the code and even different crates.
Examples
This example shows how to document unsafe functions following best practices:
/// Converts a `u8` to a `bool`
///
/// # Safety
///
/// Callers of this function are responsible that
/// these preconditions are satisfied:
///
/// * The `u8` must be either 0 or 1
///
unsafe fn num_to_bool(num: u8) -> bool {
std::mem::transmute(num)
}
This example uses the above function:
fn get_bit_of(num: u8, bit_index: u8) -> bool {
assert!(bit_index < 8);
let num = (num >> bit_index) & 1;
// Safety:
// Because of the bitwise AND operation above, num is
// guaranteed to be 0 or 1, so the following is safe
unsafe { num_to_bool(num) }
}
Writing unsafe code
If you think that you need to resort to unsafe
, there are some things you should consider:
- Read the article about Undefined Behavior and the relevant parts of the Rustonomicon and the Unsafe Code Guidelines.
- When writing unsafe code, document it carefully.
- Benchmark your code or look at the generated assembly, to verify that the unsafe code actually performs better than the equivalent safe code.
- Ask other programmers for a code review.
- Run your code in Miri.
- Add unit tests to your code.
- Fuzz your code.
Building safe abstractions
One of Rust's key patterns is isolating unsafe code into small, manageable sections, each performing a specific distinct task. This enables higher-level logic to only use idiomatic safe Rust code, without worrying about unsafe details — and that property of safe code is one of Rust's selling points as a whole. Ideally, all unsafe code should be designed with the intent to wrap potentially unsafe operations in a safe interface, "converting" undefined behavior conditions to panics and errors.
The term "safe abstraction" refers to one specific way of wrapping a certain set of unsafe operations into a safe public interface, and one single set of unsafe operations does not necessarily have one single way of being wrapped in a safe abstraction.
RefCell
as an example of a safe abstraction
This shared mutability container is a great example of how a safe abstraction can wrap an unsafe interace in its own way with its own strong points and drawbacks. The implementation backbone is UnsafeCell
— on top of that unsafe primitive, RefCell
creates a safe interface, using runtime checks to ensure the validity of operations. Here's a typical formulation of this relationship: "RefCell
is a safe abstraction over UnsafeCell
which uses borrow checker styled checks at runtime to ensure safety".
RefCell
, has its drawbacks, however, which may or may not matter in a specific use case. The primary trade-off is that because borrowing rules are enforced at runtime, the borrow checker cannot help with checking accesses to the contents of the RefCell
— the only way to know whether the code is correct is to actually see how it runs. The whole point of RefCell
, however, is to provide more complex borrowing patterns which cannot be accepted by the borrow checker, which means that those complex borrowing patterns can be really hard to test for correctness. Another trade-off is that RefCell
cannot be shared between threads.
Most unsafe operations cannot be exhaustively covered by just one implementation of a safe abstraction. Considering again the RefCell
/UnsafeCell
example, there are alternatives to RefCell
which wrap the same unsafe internals — UnsafeCell
— using different semantics, different interface and a different set of trade-offs. Those include, but are not limited to:
Cell
, which does not perform checks of any kind — it has better performance, at the cost of not being able to return references to the contents (operating only withset
/get
/update
semantics). Similarly toRefCell
, it cannot be shared between threads safely.Mutex
/RwLock
, which are thread-safe versions ofRefCell
used for sharing mutable data between threads. Those have a bigger runtime borrow-checking overhead (the typical behavior is to wait for the other thread to finish using the shared resource), but have no limitations regarding threading.- The
0.5.4 crate provides four alternatives toqcell
RefCell
, each with trade-offs and strong points of their own.