In "Programming Rust, 2nd Edition" by Jim Blandy, Jason Orendorff, Leonora F.S. Tindall on page 520 there is a graph that shows Send and Sync with overlapping circles with Sync totally subsumed by Send.
This leads me to believe that everything that implements Sync must also implement Send, but this example from page 561 and everything I've seen always specifies them both seperately,
type GenericError = Box<dyn std::error::Error + Send + Sync + 'static>
Why if 100% of things that implement Sync are also Send, is Sync not a subtrait of Send? Why trait bounds need to specify both? Why do people mark both. Is there any usecase of something to be Sync and not Send? In what case, can you share a mutable reference with another thread, but not grant ownership to that other thread?
When a type implements the Send
trait, that allows you to perform the following actions:
When a type implements the Sync
trait, that allows you to perform the following actions:
Another way to "define" the meaning of T: Sync
is simply as whether it would be safe for &T
to be Send
. This makes sense because an &T
can be copied, so making copies and sending them to different threads would allow parallel immutable access.
If a value is Send + !Sync
, then it may be accessed in any way from any thread, but only from one thread at the time, even if the access is immutable.
If a value is !Send + Sync
, then it may be accessed immutably from any thread, and from several threads in parallel. However, mutable access must happen on the thread it was created on.
Some examples:
MutexGuard
- destroying a MutexGuard
on another thread is unsound, so it can't be Send
. However if the value inside may be immutably accessed from several threads in parallel, then such an immutable access would also be safe on the MutexGuard
itself.SyncWrapper
- an immutable reference to a SyncWrapper<T>
does not allow you to perform any actions at all, so it is always safe for it to be Sync
. (The linked crate requires the inner value to be Send
, but this is stricter than necessary)&T
- since immutable references can be copied, the ability to send one to another thread would let you perform immutable access from several threads in parallel. Thus &T
can only be Send
if T
is Sync
. There is no need for T
to be Send
as an &T
doesn't allow mutable access.&mut T
- mutable references can't be copied, so sending them to other threads doesn't allow access from several threads in parallel, thus &mut T
can be Send
even if T
is not Sync
. Of course, T
must still be Send
.Arc<T>
- this mostly behaves like &T
. It can be cloned, so sending it to other threads requires T: Sync
. However, it also requires T: Send
as the last Arc<T>
might be dropped on a different thread than where T
was created, which you can't do without Send
.RefCell<T>
- this type can never be Sync
because you can modify the value inside with only an immutable reference, and this would be a data race if you could do it from several threads in parallel. There's no problem with RefCell<T>
being Send
provided that T
is.Rc<T>
- if you have two clones of the same Rc<T>
, then it would be a data race to access them from different threads in parallel. This rules out both Send
and Sync
, since both of them would allow immutable access from other threads, and that other thread could use that to call .clone()
remotely and obtain an Rc<T>
on the other thread.The book appears to be wrong. The only relationship between Send
and Sync
is that T
is Sync
if and only if &T
is Send
(it makes sense since "syncing" across threads is really just being able to share a reference to it across threads). In fact, there's even a type in the standard library which is Sync
but not Send
: MutexGuard
. The reasoning is that the underlying implementation results in undefined behavior when trying to unlock a mutex from a thread other than the thread that locked it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With