@RalfJung Good point, I've fixed the &T from "immutable reference" to "shared reference".
@steffahn Good point, the documentation has 2 target audiences: safe users and unsafe users. I'm mostly concerned about unsafe users, because safe users don't really need to understand type semantics because they don't need to prove anything. So it's true that for safe users, the most common usage of those types is probably the best definition. And it's fine if the definition is actually incorrect in some rare cases.
So in the rest of this post let's just focus on how Send and Sync should be documented in the Rustonomicon.
Note that there's a TODO at the end of the Send and Sync section (so this looks like a hole planned to be filled):
TODO: better explain what can or can't be Send or Sync. Sufficient to appeal only to data races?
Note also that similarly to shared references, the current definition of Sync is correct. The imprecision is more about Send (similarly to mutable references).
Let me try to illustrate and compare the definitions for Send using Mutex, PinMutex (some type I made up but maybe it exists), and SendWrapper (the type Ralf mentioned that may exist).
Mutex
We want to prove this line (i.e. write some SAFETY
documentation):
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
Old version
Mutex<T> can be shared between threads as long as T can be sent between threads. The requirement is needed because Mutex<T> provides &mut T to different threads which is the same as sending T between threads (think mem::swap
). It doesn't require T to be Sync because it doesn't share T between different threads.
New version
There is no thread restriction for shared access of Mutex<T> as long as there is no thread restriction of exclusive access of T. The requirement is needed because Mutex<T> provides exclusive access to T to different threads. It doesn't require T to be Sync because it doesn't provide shared access to T to different threads.
PinMutex
This would be something like Pin<Mutex<T>> but such that you can lock the mutex and get access to Pin<&mut T>. We want to prove this line:
unsafe impl<T: ?Sized + Send> Sync for PinMutex<T> {}
Old version
PinMutex<T> can be shared between threads as long as T can be sent between threads. The requirement is needed because PinMutex<T> provides Pin<&mut T> to different threads which would be the same as sending T between threads (think mem::swap
) if it weren't pinned. [Insert some argument about why that makes sense.] It doesn't require T to be Sync because it doesn't share T between different threads.
New version
Same argument as for Mutex<T>. The fact that we can't send T between threads is irrelevant.
SendWrapper
SendWrapper<T> only provides the following API:
impl<T> SendWrapper<T> {
fn pack(value: T) -> Self;
// Panics if not called from the same thread as the one that packed it.
fn unpack(self) -> T;
}
We want to prove this line:
unsafe impl<T> Send for SendWrapper<T> {}
Old version
SendWrapper<T> can be sent between threads. Even though sending SendWrapper<T> between threads results in the bytes of T being sent between threads, T cannot be used in other threads as long as it's packed, so it's not really sent in that sense. Since it can only be unpacked by the same thread that packed it, exclusive access to T is always used from that thread.
New version
There is no thread restriction for exclusive access of SendWrapper<T>, because it doesn't provide any type of access to T (shared or exclusive) as long as it's packed. Since it can only be unpacked by the same thread that packed it, exclusive access to T is always used from that thread.