Send and Sync
Not everything obeys inherited mutability, though. Some types allow you tohave multiple aliases of a location in memory while mutating it. Unless these types usesynchronization to manage this access, they are absolutely not thread-safe. Rustcaptures this through the Send
and Sync
traits.
- A type is Send if it is safe to send it to another thread.
- A type is Sync if it is safe to share between threads (
&T
is Send).
Send and Sync are fundamental to Rust’s concurrency story. As such, asubstantial amount of special tooling exists to make them work right. First andforemost, they’re unsafe traits. This means that they are unsafe toimplement, and other unsafe code can assume that they are correctlyimplemented. Since they’re marker traits (they have no associated items likemethods), correctly implemented simply means that they have the intrinsicproperties an implementor should have. Incorrectly implementing Send or Sync cancause Undefined Behavior.
Send and Sync are also automatically derived traits. This means that, unlikeevery other trait, if a type is composed entirely of Send or Sync types, then itis Send or Sync. Almost all primitives are Send and Sync, and as a consequencepretty much all types you’ll ever interact with are Send and Sync.
Major exceptions include:
- raw pointers are neither Send nor Sync (because they have no safety guards).
UnsafeCell
isn’t Sync (and thereforeCell
andRefCell
aren’t).Rc
isn’t Send or Sync (because the refcount is shared and unsynchronized).
Rc
and UnsafeCell
are very fundamentally not thread-safe: they enableunsynchronized shared mutable state. However raw pointers are, strictlyspeaking, marked as thread-unsafe as more of a lint. Doing anything usefulwith a raw pointer requires dereferencing it, which is already unsafe. In thatsense, one could argue that it would be “fine” for them to be marked as threadsafe.
However it’s important that they aren’t thread-safe to prevent types thatcontain them from being automatically marked as thread-safe. These types havenon-trivial untracked ownership, and it’s unlikely that their author wasnecessarily thinking hard about thread safety. In the case of Rc
, we have a niceexample of a type that contains a *mut
that is definitely not thread-safe.
Types that aren’t automatically derived can simply implement them if desired:
struct MyBox(*mut u8);
unsafe impl Send for MyBox {}
unsafe impl Sync for MyBox {}
In the incredibly rare case that a type is inappropriately automaticallyderived to be Send or Sync, then one can also unimplement Send and Sync:
#![feature(optin_builtin_traits)]
// I have some magic semantics for some synchronization primitive!
struct SpecialThreadToken(u8);
impl !Send for SpecialThreadToken {}
impl !Sync for SpecialThreadToken {}
Note that in and of itself it is impossible to incorrectly derive Send andSync. Only types that are ascribed special meaning by other unsafe code canpossible cause trouble by being incorrectly Send or Sync.
Most uses of raw pointers should be encapsulated behind a sufficient abstractionthat Send and Sync can be derived. For instance all of Rust’s standardcollections are Send and Sync (when they contain Send and Sync types) in spiteof their pervasive use of raw pointers to manage allocations and complex ownership.Similarly, most iterators into these collections are Send and Sync because theylargely behave like an &
or &mut
into the collection.
TODO: better explain what can or can’t be Send or Sync. Sufficient to appealonly to data races?