You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nomicon/safe-unsafe-meaning.md

6.1 KiB

% How Safe and Unsafe Interact

What's the relationship between Safe Rust and Unsafe Rust? How do they interact?

The separation between Safe Rust and Unsafe Rust is controlled with the unsafe keyword, which acts as an interface from one to the other. This is why we can say Safe Rust is a safe language: all the unsafe parts are kept exclusively behind the boundary.

The unsafe keyword has two uses: to declare the existence of contracts the compiler can't check, and to declare that the adherence of some code to those contracts has been checked by the programmer.

You can use unsafe to indicate the existence of unchecked contracts on functions and on trait declarations. On functions, unsafe means that users of the function must check that function's documentation to ensure they are using it in a way that maintains the contracts the function requires. On trait declarations, unsafe means that implementors of the trait must check the trait documentation to ensure their implementation maintains the contracts the trait requires.

You can use unsafe on a block to declare that all constraints required by an unsafe function within the block have been adhered to, and the code can therefore be trusted. You can use unsafe on a trait implementation to declare that the implementation of that trait has adhered to whatever contracts the trait's documentation requires.

There is also the #[unsafe_no_drop_flag] attribute, which exists for historic reasons and is being phased out. See the section on drop flags for details.

The standard library has a number of unsafe functions, including:

  • slice::get_unchecked, which performs unchecked indexing, allowing memory safety to be freely violated.
  • mem::transmute reinterprets some value as having a given type, bypassing type safety in arbitrary ways (see conversions for details).
  • Every raw pointer to a sized type has an intrinstic offset method that invokes Undefined Behavior if the passed offset is not "in bounds" as defined by LLVM.
  • All FFI functions are unsafe because the other language can do arbitrary operations that the Rust compiler can't check.

As of Rust 1.0 there are exactly two unsafe traits:

  • Send is a marker trait (a trait with no API) that promises implementors are safe to send (move) to another thread.
  • Sync is a marker trait that promises threads can safely share implementors through a shared reference.

Much of the Rust standard library also uses Unsafe Rust internally, although these implementations are rigorously manually checked, and the Safe Rust interfaces provided on top of these implementations can be assumed to be safe.

The need for all of this separation boils down a single fundamental property of Safe Rust:

No matter what, Safe Rust can't cause Undefined Behavior.

The design of the safe/unsafe split means that Safe Rust inherently has to trust that any Unsafe Rust it touches has been written correctly (meaning the Unsafe Rust actually maintains whatever contracts it is supposed to maintain). On the other hand, Unsafe Rust has to be very careful about trusting Safe Rust.

As an example, Rust has the PartialOrd and Ord traits to differentiate between types which can "just" be compared, and those that provide a total ordering (where every value of the type is either equal to, greater than, or less than any other value of the same type). The sorted map type BTreeMap doesn't make sense for partially-ordered types, and so it requires that any key type for it implements the Ord trait. However, BTreeMap has Unsafe Rust code inside of its implementation, and this Unsafe Rust code cannot assume that any Ord implementation it gets makes sense. The unsafe portions of BTreeMap's internals have to be careful to maintain all necessary contracts, even if a key type's Ord implementation does not implement a total ordering.

Unsafe Rust cannot automatically trust Safe Rust. When writing Unsafe Rust, you must be careful to only rely on specific Safe Rust code, and not make assumptions about potential future Safe Rust code providing the same guarantees.

This is the problem that unsafe traits exist to resolve. The BTreeMap type could theoretically require that keys implement a new trait called UnsafeOrd, rather than Ord, that might look like this:

use std::cmp::Ordering;

unsafe trait UnsafeOrd {
    fn cmp(&self, other: &Self) -> Ordering;
}

Then, a type would use unsafe to implement UnsafeOrd, indicating that they've ensured their implementation maintains whatever contracts the trait expects. In this situation, the Unsafe Rust in the internals of BTreeMap could trust that the key type's UnsafeOrd implementation is correct. If it isn't, it's the fault of the unsafe trait implementation code, which is consistent with Rust's safety guarantees.

The decision of whether to mark a trait unsafe is an API design choice. Rust has traditionally avoided marking traits unsafe because it makes Unsafe Rust pervasive, which is not desirable. Send and Sync are marked unsafe because thread safety is a fundamental property that unsafe code can't possibly hope to defend against in the way it could defend against a bad Ord implementation. The decision of whether to mark your own traits unsafe depends on the same sort of consideration. If unsafe code cannot reasonably expect to defend against a bad implementation of the trait, then marking the trait unsafe is a reasonable choice.

As an aside, while Send and Sync are unsafe traits, they are automatically implemented for types when such derivations are provably safe to do. Send is automatically derived for all types composed only of values whose types also implement Send. Sync is automatically derived for all types composed only of values whose types also implement Sync.

This is the dance of Safe Rust and Unsafe Rust. It is designed to make using Safe Rust as ergonomic as possible, but requires extra effort and care when writing Unsafe Rust. The rest of the book is largely a discussion of the sort of care that must be taken, and what contracts it is expected of Unsafe Rust to uphold.