Rewrote "How Safe and Unsafe Interact" Nomicon chapter.

The previous version of the chapter covered a lot of ground, but was a little
meandering and hard to follow at times. This draft is intended to be clearer
and more direct, while still providing the same information as the previous
version.
pull/10/head
Andrew Brinker 9 years ago committed by Manish Goregaokar
parent 43185c0b2e
commit bab35d7cdc

@ -1,150 +1,131 @@
% How Safe and Unsafe Interact % How Safe and Unsafe Interact
So what's the relationship between Safe and Unsafe Rust? How do they interact? What's the relationship between Safe Rust and Unsafe Rust? How do they
interact?
Rust models the separation between Safe and Unsafe Rust with the `unsafe`
keyword, which can be thought as a sort of *foreign function interface* (FFI) The separation between Safe Rust and Unsafe Rust is controlled with the
between Safe and Unsafe Rust. This is the magic behind why we can say Safe Rust `unsafe` keyword, which acts as a sort of *foreign function interface*
is a safe language: all the scary unsafe bits are relegated exclusively to FFI from one to the other. This boundary is why we can say Safe Rust is a
*just like every other safe language*. safe language: all the unsafe parts are kept exclusively behind the FFI
boundary, *just like any other safe language*. Best of all, because Safe
However because one language is a subset of the other, the two can be cleanly Rust is a subset of Unsafe Rust, the two can be cleanly intermixed,
intermixed as long as the boundary between Safe and Unsafe Rust is denoted with without headers, runtimes, or any other FFI boilerplate.
the `unsafe` keyword. No need to write headers, initialize runtimes, or any of
that other FFI boiler-plate. The `unsafe` keyword has dual purposes: to declare the existence of
contracts the compiler can't check, and to declare that the adherence
There are several places `unsafe` can appear in Rust today, which can largely be of some code to those contracts has been checked by the programmer,
grouped into two categories: and the code can therefore be trusted.
* There are unchecked contracts here. To declare you understand this, I require You can use `unsafe` to indicate the existence of unchecked contracts on
you to write `unsafe` elsewhere: _functions_ and on _trait declarations_. On functions, `unsafe` means that
* On functions, `unsafe` is declaring the function to be unsafe to call. users of the function must check that function's documentation to ensure
Users of the function must check the documentation to determine what this they are using it in a way that maintains the contracts the function
means, and then have to write `unsafe` somewhere to identify that they're requires. On trait declarations, `unsafe` means that implementors of the
aware of the danger. trait must check the trait documentation to ensure their implementation
* On trait declarations, `unsafe` is declaring that *implementing* the trait maintains the contracts the trait requires.
is an unsafe operation, as it has contracts that other unsafe code is free
to trust blindly. (More on this below.) 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
* I am declaring that I have, to the best of my knowledge, adhered to the can therefore be trusted. You can use `unsafe` on a trait implementation
unchecked contracts: to declare that the implementation of that trait has adhered to whatever
* On trait implementations, `unsafe` is declaring that the contract of the contracts the trait's documentation requires.
`unsafe` trait has been upheld.
* On blocks, `unsafe` is declaring any unsafety from an unsafe There is also the `#[unsafe_no_drop_flag]` attribute, which exists for
operation within to be handled, and therefore the parent function is safe. historic reasons and is being phased out. See the section on [drop flags]
for details.
There is also `#[unsafe_no_drop_flag]`, which is a special case that exists for
historical reasons and is in the process of being phased out. See the section on The standard library has a number of unsafe functions, including:
[drop flags] for details.
* `slice::get_unchecked`, which performs unchecked indexing, allowing
Some examples of unsafe functions: memory safety to be freely violated.
* `mem::transmute` reinterprets some value as having a given type, bypassing
* `slice::get_unchecked` will perform unchecked indexing, allowing memory type safety in arbitrary ways (see [conversions] for details).
safety to be freely violated. * Every raw pointer to a sized type has an intrinstic `offset` method that
* every raw pointer to sized type has intrinsic `offset` method that invokes invokes Undefined Behavior if the passed offset is not "in bounds" as
Undefined Behavior if it is not "in bounds" as defined by LLVM. defined by LLVM.
* `mem::transmute` reinterprets some value as having the given type, * All FFI functions are `unsafe` because the other language can do arbitrary
bypassing type safety in arbitrary ways. (see [conversions] for details) operations that the Rust compiler can't check.
* All FFI functions are `unsafe` because they can do arbitrary things.
C being an obvious culprit, but generally any language can do something
that Rust isn't happy about.
As of Rust 1.0 there are exactly two unsafe traits: As of Rust 1.0 there are exactly two unsafe traits:
* `Send` is a marker trait (it has no actual API) that promises implementors * `Send` is a marker trait (a trait with no API) that promises implementors are
are safe to send (move) to another thread. safe to send (move) to another thread.
* `Sync` is a marker trait that promises that threads can safely share * `Sync` is a marker trait that promises threads can safely share implementors
implementors through a shared reference. through a shared reference.
The need for unsafe traits boils down to the fundamental property of safe code: Much of the Rust standard library also uses Unsafe Rust internally, although
these implementations are rigorously manually checked, and the Safe Rust
**No matter how completely awful Safe code is, it can't cause Undefined interfaces provided on top of these implementations can be assumed to be safe.
Behavior.**
The need for all of this separation boils down a single fundamental property
This means that Unsafe Rust, **the royal vanguard of Undefined Behavior**, has to be of Safe Rust:
*super paranoid* about generic safe code. To be clear, Unsafe Rust is totally free to trust
specific safe code. Anything else would degenerate into infinite spirals of **No matter what, Safe Rust can't cause Undefined Behavior.**
paranoid despair. In particular it's generally regarded as ok to trust the standard library
to be correct. `std` is effectively an extension of the language, and you The design of the safe/unsafe split means that Safe Rust inherently has to
really just have to trust the language. If `std` fails to uphold the trust that any Unsafe Rust it touches has been written correctly (meaning
guarantees it declares, then it's basically a language bug. the Unsafe Rust actually maintains whatever contracts it is supposed to
maintain). On the other hand, Unsafe Rust has to be very careful about
That said, it would be best to minimize *needlessly* relying on properties of trusting Safe Rust.
concrete safe code. Bugs happen! Of course, I must reinforce that this is only
a concern for Unsafe code. Safe code can blindly trust anyone and everyone As an example, Rust has the `PartialOrd` and `Ord` traits to differentiate
as far as basic memory-safety is concerned. 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,
On the other hand, safe traits are free to declare arbitrary contracts, but because or less than any other value of the same type). The sorted map type
implementing them is safe, unsafe code can't trust those contracts to actually `BTreeMap` doesn't make sense for partially-ordered types, and so it
be upheld. This is different from the concrete case because *anyone* can requires that any key type for it implements the `Ord` trait. However,
randomly implement the interface. There is something fundamentally different `BTreeMap` has Unsafe Rust code inside of its implementation, and this
about trusting a particular piece of code to be correct, and trusting *all the Unsafe Rust code cannot assume that any `Ord` implementation it gets makes
code that will ever be written* to be correct. 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
For instance Rust has `PartialOrd` and `Ord` traits to try to differentiate does not implement a total ordering.
between types which can "just" be compared, and those that actually implement a
total ordering. Pretty much every API that wants to work with data that can be Unsafe Rust cannot automatically trust Safe Rust. When writing Unsafe Rust,
compared wants Ord data. For instance, a sorted map like BTreeMap you must be careful to only rely on specific Safe Rust code, and not make
*doesn't even make sense* for partially ordered types. If you claim to implement assumptions about potential future Safe Rust code providing the same
Ord for a type, but don't actually provide a proper total ordering, BTreeMap will guarantees.
get *really confused* and start making a total mess of itself. Data that is
inserted may be impossible to find! This is the problem that `unsafe` traits exist to resolve. The `BTreeMap`
type could theoretically require that keys implement a new trait called
But that's okay. BTreeMap is safe, so it guarantees that even if you give it a `UnsafeOrd`, rather than `Ord`, that might look like this:
completely garbage Ord implementation, it will still do something *safe*. You
won't start reading uninitialized or unallocated memory. In fact, BTreeMap
manages to not actually lose any of your data. When the map is dropped, all the
destructors will be successfully called! Hooray!
However BTreeMap is implemented using a modest spoonful of Unsafe Rust (most collections
are). That means that it's not necessarily *trivially true* that a bad Ord
implementation will make BTreeMap behave safely. BTreeMap must be sure not to rely
on Ord *where safety is at stake*. Ord is provided by safe code, and safety is not
safe code's responsibility to uphold.
But wouldn't it be grand if there was some way for Unsafe to trust some trait
contracts *somewhere*? This is the problem that unsafe traits tackle: by marking
*the trait itself* as unsafe to implement, unsafe code can trust the implementation
to uphold the trait's contract. Although the trait implementation may be
incorrect in arbitrary other ways.
For instance, given a hypothetical UnsafeOrd trait, this is technically a valid
implementation:
```rust ```rust
# use std::cmp::Ordering; use std::cmp::Ordering;
# struct MyType;
# unsafe trait UnsafeOrd { fn cmp(&self, other: &Self) -> Ordering; } unsafe trait UnsafeOrd {
unsafe impl UnsafeOrd for MyType { fn cmp(&self, other: &Self) -> Ordering;
fn cmp(&self, other: &Self) -> Ordering {
Ordering::Equal
}
} }
``` ```
But it's probably not the implementation you want. Then, a type would use `unsafe` to implement `UnsafeOrd`, indicating that
they've ensured their implementation maintains whatever contracts the
Rust has traditionally avoided making traits unsafe because it makes Unsafe trait expects. In this situation, the Unsafe Rust in the internals of
pervasive, which is not desirable. The reason Send and Sync are unsafe is because thread `BTreeMap` could trust that the key type's `UnsafeOrd` implementation is
safety is a *fundamental property* that unsafe code cannot possibly hope to defend correct. If it isn't, it's the fault of the unsafe trait implementation
against in the same way it would defend against a bad Ord implementation. The code, which is consistent with Rust's safety guarantees.
only way to possibly defend against thread-unsafety would be to *not use
threading at all*. Making every load and store atomic isn't even sufficient, The decision of whether to mark a trait `unsafe` is an API design choice.
because it's possible for complex invariants to exist between disjoint locations Rust has traditionally avoided marking traits unsafe because it makes Unsafe
in memory. For instance, the pointer and capacity of a Vec must be in sync. Rust pervasive, which is not desirable. `Send` and `Sync` are marked unsafe
because thread safety is a *fundamental property* that unsafe code can't
Even concurrent paradigms that are traditionally regarded as Totally Safe like possibly hope to defend against in the way it could defend against a bad
message passing implicitly rely on some notion of thread safety -- are you `Ord` implementation. The decision of whether to mark your own traits `unsafe`
really message-passing if you pass a pointer? Send and Sync therefore require depends on the same sort of consideration. If `unsafe` code cannot reasonably
some fundamental level of trust that Safe code can't provide, so they must be expect to defend against a bad implementation of the trait, then marking the
unsafe to implement. To help obviate the pervasive unsafety that this would trait `unsafe` is a reasonable choice.
introduce, Send (resp. Sync) is automatically derived for all types composed only
of Send (resp. Sync) values. 99% of types are Send and Sync, and 99% of those As an aside, while `Send` and `Sync` are `unsafe` traits, they are
never actually say it (the remaining 1% is overwhelmingly synchronization automatically implemented for types when such derivations are provably safe
primitives). 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.
[drop flags]: drop-flags.html [drop flags]: drop-flags.html
[conversions]: conversions.html [conversions]: conversions.html

Loading…
Cancel
Save