Rust's `Send` and `Sync`, but actually the opposite
Tags: rust
Published: 2023-03-28 15:33:46

> This post is my personal notes for grokking `Send` and `Sync` in Rust. It's not formal, and will assume that you're basically familiar with concurrency and synchronisation, as well as Rust's main wrapper types. In particular, remember that Rust values are always owned by exactly one variable and taking references must satisfy [**aliasing** xor **mutability**](https://doc.rust-lang.org/stable/std/cell/index.html).

**Here's the secret**: you shouldn't be worrying about `Send` and `Sync`. They're the default. Almost everything is `Send` and `Sync`, and the compiler will auto-derive them for every type it can. The issue is `!Send` and `!Sync`, or really just: `!Send`.

## So what is `!Send`?

A type is `!Send` when values can't be owned on one thread and then moved to another. Because of single-ownership it couldn't be owned by two threads simultaneously, this is a restriction across the whole life of the value.

> `!Send` := this value is locked to the thread that created it

That's the core concept behind both `!Send` and `!Sync`. I'll get to when this is the case later, but first let's talk references.

If we can have `T: !Send`, we can also have `&U: !Send` since `T` could be `&U`. This case is particularly interesting, since if we own a value of type `T` we can create as many `&T` values as we like.

This means that unless `&T: !Send`, we can have as many `&T` values on as many threads as we like. This is great for the most part: `&T` is immutable so there are no data-races... *unless* `T` contains [interior mutability](https://doc.rust-lang.org/stable/std/cell/index.html). Interior mutability exactly means being able to mutate `T` behind a `&T` reference. This sounds like a recipe for data races! In such cases we'll need `&T: !Send` to prevent them. This is so important that it gets its own name...

## Surprise `!Sync`!

`T: !Sync` simply means `&T: !Send`. Interpreting a bit, `!Sync` means that a value cannot be referenced by multiple threads at all.

> `!Sync` := references to this value are locked to its thread

This *almost* means that `!Send` implies `!Sync`. After all, if a value cannot be used by more than one thread at different times, how could it possibly be allowed by *more than one* thread at the same time? This is often true, but not a logical requirement, because `!Sync` is about whether **shared references** (`&T`) can be used on multiple threads at the same time, not the value itself. It is possible (but fairly rare) for a type to be `!Send` but still `Sync`, for example if your type is backed by some thread-local resource but all behaviour visible through `&T` does not depend on it.

## So is this type `!Send` or `!Sync`?

There are a bunch of rules of thumb. But I think the key question they boil down to is: could this type be used to move a `!Send` value (which may be a `&T: !Send`) to another thread?

Rules of thumb for `!Sync`:
1. Your type transitively contains any `!Sync` type, unless wrapped by a [`Mutex`](https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html#impl-Sync-for-Mutex%3CT%3E) or similar synchronisation primitive.
2. Your type contains interior mutability which is not synchronised. For example, it contains [`Cell`](https://doc.rust-lang.org/stable/std/cell/struct.Cell.html) or [`RefCell`](https://doc.rust-lang.org/stable/std/cell/struct.RefCell.html).
3. If you can use a `&T` to take ownership of any `!Send` type.
  - This is normally the case if your type is `!Send` itself.
4. Your type contains raw pointers and you haven't manually proven and implemented `Sync`.

Rules of thumb for `!Send`:
1. Your type transitively contains any `!Send` type.
2. Your type is a handle to a resource which it owns non-uniquely, and access to that resource is not synchronised.
  - For `&T`, this is exactly rule 2 for `!Sync`, since if `T` has interior mutability that means that `&T` is a shared-ownership handle to `T`.
3. Your type contains raw pointers and you haven't manually proven and implemented `Send`.

### Examples

- **`Rc`** -- This is a handle to a resource that is jointly owned, therefore `!Send` since (for example) `Rc::get_mut` is not synchronised. Moreover, `Rc` has interior mutability for the reference count, which is unsynchronised, so `!Sync`.
- **`Arc`** - Avoids the problems of `Rc` by synchronising the reference count and access appropriately using atomics, thus both `Send` and `Sync`.
- **`RefCell`** -- Archetypal example of interior mutability with no synchronisation, therefore `!Sync`, however since the wrapped value is unqiuely owned then `RefCell` is `Send` when the wrapped value is.
- **`Mutex`** -- If it contains a `!Send` type then it's `!Send + !Sync` since it provides full ownership of the contained type. Otherwise, it is both `Send` by unique ownership of a `Send`, and `Sync` by enforcing synchronisation itself.

Raw pointers are interesting. Rust marks all raw pointer types as `!Send` and `!Sync`, but moving them (and their references) between threads isn't in-and-of-itself a problem. The problem comes when you try to *use* (that is, dereference) that pointer. That action is already marked as `unsafe`, so Rust could have allowed them to be `Send` and `Sync`, but it is considered so easy to break `Send` and `Sync` with raw pointers that you need to **additionally** implement the corresponding `unsafe` traits to mark your type as `Send` or `Sync`.

## No free `Send` wrapping

"I've got this annoying value, how do I just make the damn thing `Send` and `Sync` already!?" I hear you cry.

Bad news, I'm afriad.

The better news is that if the type is `!Sync` but is `Send`, then you can wrap it in a [`Mutex`](https://doc.rust-lang.org/stable/std/sync/struct.Mutex.html#impl-Sync-for-Mutex%3CT%3E) or similar synchronisation type and that will make it both `Send` and `Sync`.

The very bad news is that `!Send` types can only be made `Send` by `unsafe impl Send for T` -- which you should absolutely not do unless you very much know what you're doing.

Truly `!Send` types (that is, basically anything `!Send` except carefully used raw pointers) are stuck on their thread. This is the entire point of the feature, anything else and you're exposed to data races.

Your alternatives for dealing with `!Send` types is to, for example:
1. Serialise the data contained and send that to another thread where it can be re-constructed.
2. Use channels or other inter-thread communication to indirectly "talk to" the `!Send` thread when needed.

## When do I force `!Send` or `!Sync`?

It's possible that you're writing some struct that would make no sense to send to other threads (or send references to other threads), but the compiler cannot work this out itself. This is rare, since the compiler will generally work it out before you, but possible if the issue is one of higher-level correctness that the compiler cannot reason about.

For example, suppose you're wrapping some library behind FFI and you know (from the library docs) that the resource you're working with is thread-local. However, the "handle" that library gives you to said resource is just a bare primitive, like `u32`. Rust has no idea that `u32` is `!Send` (acting more like a pointer) until you tell it.

Right now, it's not terribly easy to force `!Send` or `!Sync`, since [negative impls](https://doc.rust-lang.org/unstable-book/language-features/negative-impls.html) are only available on nightly. The work around is to use a [`PhantomData`](https://doc.rust-lang.org/std/marker/struct.PhantomData.html) of some type that already has the `!Send` or `!Sync` you require, so that gets inherited. For example, [`winit::EventLoop`](https://docs.rs/winit/0.28.3/winit/event_loop/struct.EventLoop.html) contains a `PhantomData<*mut ()>` explicitly for this purpose.

# When do I force `Send` or `Sync`?

It is, of course, possible to manually implement `Send` and `Sync` on something the compiler has decided is `!Send` and `!Sync`. This is how `std` collections (as well as others) which work on raw pointers implement `Send` and `Sync` appropriately.

This power -- like any use of `unsafe` -- should absolutely not be taken lightly. Read [the nomicon](https://doc.rust-lang.org/nomicon/send-and-sync.html), reason carefully, and write good tests. Don't just `unsafe impl Send` because you're frustrated, that way lies Undefined Behaviour and Madness.