Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is the Sync trait a strict subset of the Send trait; what implements Sync without Send?

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.

Figure 19-9. Send and Sync types

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?

like image 391
NO WAR WITH RUSSIA Avatar asked Dec 05 '22 08:12

NO WAR WITH RUSSIA


2 Answers

When a type implements the Send trait, that allows you to perform the following actions:

  1. Mutably access the value from threads other than the one it was created on. This includes dropping it.
  2. Immutably access the value from threads other than the one it was created from. (But not necessarily in parallel.)

When a type implements the Sync trait, that allows you to perform the following actions:

  1. Immutably access the value from several threads in parallel.

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.
like image 129
Alice Ryhl Avatar answered Jan 13 '23 01:01

Alice Ryhl


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.

like image 22
Aplet123 Avatar answered Jan 13 '23 00:01

Aplet123