|
|
|
@ -5,7 +5,7 @@ So what's the relationship between Safe 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)
|
|
|
|
|
between Safe and Unsafe Rust. This is the magic behind why we can say Safe Rust
|
|
|
|
|
is a safe language: all the scary unsafe bits are relegated *exclusively* to FFI
|
|
|
|
|
is a safe language: all the scary unsafe bits are relegated exclusively to FFI
|
|
|
|
|
*just like every other safe language*.
|
|
|
|
|
|
|
|
|
|
However because one language is a subset of the other, the two can be cleanly
|
|
|
|
@ -61,13 +61,13 @@ The need for unsafe traits boils down to the fundamental property of safe code:
|
|
|
|
|
**No matter how completely awful Safe code is, it can't cause Undefined
|
|
|
|
|
Behaviour.**
|
|
|
|
|
|
|
|
|
|
This means that Unsafe, **the royal vanguard of Undefined Behaviour**, has to be
|
|
|
|
|
*super paranoid* about generic safe code. Unsafe is free to trust *specific* safe
|
|
|
|
|
code (or else you would degenerate into infinite spirals of paranoid despair).
|
|
|
|
|
It is generally regarded as ok to trust the standard library to be correct, as
|
|
|
|
|
`std` is effectively an extension of the language (and you *really* just have
|
|
|
|
|
to trust the language). If `std` fails to uphold the guarantees it declares,
|
|
|
|
|
then it's basically a language bug.
|
|
|
|
|
This means that Unsafe Rust, **the royal vanguard of Undefined Behaviour**, has to be
|
|
|
|
|
*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
|
|
|
|
|
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
|
|
|
|
|
really just have to trust the language. If `std` fails to uphold the
|
|
|
|
|
guarantees it declares, then it's basically a language bug.
|
|
|
|
|
|
|
|
|
|
That said, it would be best to minimize *needlessly* relying on properties of
|
|
|
|
|
concrete safe code. Bugs happen! Of course, I must reinforce that this is only
|
|
|
|
@ -75,36 +75,36 @@ a concern for Unsafe code. Safe code can blindly trust anyone and everyone
|
|
|
|
|
as far as basic memory-safety is concerned.
|
|
|
|
|
|
|
|
|
|
On the other hand, safe traits are free to declare arbitrary contracts, but because
|
|
|
|
|
implementing them is Safe, Unsafe can't trust those contracts to actually
|
|
|
|
|
implementing them is safe, unsafe code can't trust those contracts to actually
|
|
|
|
|
be upheld. This is different from the concrete case because *anyone* can
|
|
|
|
|
randomly implement the interface. There is something fundamentally different
|
|
|
|
|
about trusting a *particular* piece of code to be correct, and trusting *all the
|
|
|
|
|
about trusting a particular piece of code to be correct, and trusting *all the
|
|
|
|
|
code that will ever be written* to be correct.
|
|
|
|
|
|
|
|
|
|
For instance Rust has `PartialOrd` and `Ord` traits to try to differentiate
|
|
|
|
|
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
|
|
|
|
|
compared *really* wants Ord data. For instance, a sorted map like BTreeMap
|
|
|
|
|
total ordering. Pretty much every API that wants to work with data that can be
|
|
|
|
|
compared wants Ord data. For instance, a sorted map like BTreeMap
|
|
|
|
|
*doesn't even make sense* for partially ordered types. If you claim to implement
|
|
|
|
|
Ord for a type, but don't actually provide a proper total ordering, BTreeMap will
|
|
|
|
|
get *really confused* and start making a total mess of itself. Data that is
|
|
|
|
|
inserted may be impossible to find!
|
|
|
|
|
|
|
|
|
|
But that's okay. BTreeMap is safe, so it guarantees that even if you give it a
|
|
|
|
|
*completely* garbage Ord implementation, it will still do something *safe*. You
|
|
|
|
|
won't start reading uninitialized memory or unallocated memory. In fact, BTreeMap
|
|
|
|
|
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 (most collections
|
|
|
|
|
are). That means that it is not necessarily *trivially true* that a bad Ord
|
|
|
|
|
implementation will make BTreeMap behave safely. Unsafe must be sure not to rely
|
|
|
|
|
on Ord *where safety is at stake*. Ord is provided by Safe, and safety is not
|
|
|
|
|
Safe's responsibility to uphold.
|
|
|
|
|
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
|
|
|
|
|
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 can trust the implementation
|
|
|
|
|
*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.
|
|
|
|
|
|
|
|
|
@ -126,7 +126,7 @@ But it's probably not the implementation you want.
|
|
|
|
|
|
|
|
|
|
Rust has traditionally avoided making traits unsafe because it makes Unsafe
|
|
|
|
|
pervasive, which is not desirable. Send and Sync are unsafe is because thread
|
|
|
|
|
safety is a *fundamental property* that Unsafe cannot possibly hope to defend
|
|
|
|
|
safety is a *fundamental property* that unsafe code cannot possibly hope to defend
|
|
|
|
|
against in the same way it would defend against a bad Ord implementation. The
|
|
|
|
|
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,
|
|
|
|
@ -135,10 +135,10 @@ in memory. For instance, the pointer and capacity of a Vec must be in sync.
|
|
|
|
|
|
|
|
|
|
Even concurrent paradigms that are traditionally regarded as Totally Safe like
|
|
|
|
|
message passing implicitly rely on some notion of thread safety -- are you
|
|
|
|
|
really message-passing if you pass a *pointer*? Send and Sync therefore require
|
|
|
|
|
some *fundamental* level of trust that Safe code can't provide, so they must be
|
|
|
|
|
really message-passing if you pass a pointer? Send and Sync therefore require
|
|
|
|
|
some fundamental level of trust that Safe code can't provide, so they must be
|
|
|
|
|
unsafe to implement. To help obviate the pervasive unsafety that this would
|
|
|
|
|
introduce, Send (resp. Sync) is *automatically* derived for all types composed only
|
|
|
|
|
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
|
|
|
|
|
never actually say it (the remaining 1% is overwhelmingly synchronization
|
|
|
|
|
primitives).
|
|
|
|
|