Why impl Sync for Mutex requires T: Send

Hi,
I struggle to understand impl Sync for Mutex. Why is it not enough to require T: Sync?
After all, given a &Mutex all I can get is a &T.

Nomicon says:

  • 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).

Docs say:

Sync: Types for which it is safe to share references between threads.
The precise definition is: a type T is Sync if &Tis Send. In other words, if there is no possibility of undefined behavior (including data races) when passing &T references between threads.

Send: Types that can be transferred across thread boundaries.

How can I understand impl Sync for Mutex in this context?

1 Like

Sync is usually more restrictive than Send.
In other words everything that implements Sync usually also implements Send, but not the opposite.
This means that Mutex does you have a favour by only requiring Send instead of Sync.

To go in more details, the &mut T you obtain when you lock the mutex is not shared between threads because only the thread that locked the mutex has access to it. Therefore T: Sync is irrelevant.

1 Like

Mutex is in fact a “Sync-maker”. It makes a Sync wrapper of a sendable type that is otherwise not Sync, like for example Sender (sender end of a channel).

Like this:

let (tx, rx) = channel::<i32>();
let shared_sender = Mutex::new(tx);

// Multiple threads can now share access to `shared_sender`,
// because `Mutex<Sender<i32>>` is `Sync`, even if the naked Sender was not.

Note: The usual way is to give each thread a clone of the sender, but the Mutex use case exists too.

Compare with RwLock which is not a “Sync-maker”; the reason is that it needs to allow simultaneous read-only access.

2 Likes

Send essentially means that the type can safely be moved into another thread.

Sync essentially means that the type can be shared as &T between multiple threads.

Most types are both Send and Sync, however there are exceptions.

For instance, Cell<T> type is Send (when T is Send) because it's safe for Cell to change thread it is used in - as long it will be accessible from only one thread at the same time. However, it is not Sync, as it cannot be safely shared between multiple threads due to interior mutability.

Rc<T> type is not Send because due to possibility that there are other Rc instances in the current thread - which would make it so that there are two Rcs using the same non-atomic counter in different threads. In theory it is possible to send Rc to another thread if you can prove that this is the only reference - this can be done with get_mut or make_mut method of Rc.


Now for why Sync in Mutex requires Send as opposed to Sync. This is because Mutex<T> enforces that the mutex guard can be only obtained from a single thread. For instance, the following code is safe, because Cell is only accessed from a single thread at the same time, so there is no risk of race conditions.

For understanding purposes in this example, crossbeam::scope is a block which automatically joins all threads at end of scope spawned by scope object. It isn't part of Rust standard library, but it makes things a bit easier to understand.

extern crate crossbeam;

use std::sync::Mutex;
use std::cell::Cell;

fn main() {
    let mutex = Mutex::new(Cell::new(0));
    crossbeam::scope(|scope| for _ in 0..10 {
        scope.spawn(|| {
            let cell = mutex.lock().unwrap();
            cell.set(cell.get() + 1);
        });
    });
}

Note that Cell<T> obtained from Mutex<T> still cannot be shared between multiple threads as it's not a Sync type. This is an invalid code as it tries to make reference from Mutex<T> be shared between multiple threads.

extern crate crossbeam;

use std::sync::Mutex;
use std::cell::Cell;

fn main() {
    let mutex = Mutex::new(Cell::new(0));
    crossbeam::scope(|scope| for _ in 0..10 {
        scope.spawn(|| {
            let cell = mutex.lock().unwrap();
            crossbeam::scope(|inner_scope| {
                inner_scope.spawn(|| cell.set(cell.get() + 1));
                cell.set(cell.get() + 1)
            });
        });
    });
}

Not so fun fact: In Rust 1.18 and lower, the code I wrote above actually used to be allowed, that was a bug, and is fixed in current versions of Rust as MutexGuard<T> didn't require T to be Sync in order to be itself Sync. So if you test the above example, you may find it actually working.

(to think that if this question would be asked 2 months ago, I probably would accidentally find this issue while trying to explain why is it so)

4 Likes

Thank you very much for your answers!

The origin of my confusion comes from my suspicion that Mutex could actually work fine with less guarantees than what Send provides. Specifically, there is no clear need for T: Send but T: Sync would work too, even though also here Mutex does not need all guarantees provided by Sync and @tomaka points out that Sync is more expensive to provide, therefore Mutex requires only Send.

In the end, Mutex only needs the guarantee that &T can be used from any thread as long as only one &T exists at the same time. Mutex does not require T to be sendable, i.e. does not need T to be droppable on an arbitrary thread. But I think there does not exist a trait in current Rust which captures only this, but instead both Send and Sync contain this guarantee.

Here's how I would summarize Send and Sync in a few words:

  • If T is Send, then unique access on a different thread is safe.
  • If T is Sync, then shared access on different threads is safe.

"Unique access" includes unique ownership (sending a T by value) and unique references (&mut T). Since these are unique, they can only be sent to one thread at a time.

"Shared access" includes shared ownership (e.g. Arc<T>) and shared references (&T). Since these can be copied or cloned, they can be used to share a value across multiple threads simultaneously. But they can't be promoted to a unique reference (&mut T) unless wrapped in something like Mutex that enforces the uniqueness at run-time.

(If a type is neither Sync nor Send, then it can't safely be accessed at all except on the thread where it was created.)

3 Likes

It does, in fact. If Mutex<T> is Sync then it provides &mut T access on another thread, which allows that thread to drop the T and replace it with a different T. For example:

use std::sync::{Mutex, Arc};
use std::thread;

let v0 = Arc::new(Mutex::new(vec![0]));
let v1 = v0.clone();
thread::spawn(move || {
    *v1.lock().unwrap() = Vec::new(); // destructor for old Vec is run here
});
3 Likes

How do you get the &mut Mutex in another thread which is required for Mutex::get_mut if Mutex is not Send?

It is Send if T: Send. Sending a &mut Mutex to another thread is likely a niche/uncommon case since you can just send &mut T to begin with.

The typical way to get &mut T from Mutex<T> is Mutex::lock, which takes &self, not &mut self.

1 Like

Yes, but that does not give you &mut T but only &T so in that case there is no need for T: Send either.

Sure, Mutex is Send if T: Send, but the thought that I wanted to convey in my answer to @mbrubeck was that Mutex does not require T: Send if I don't need Mutex to be Send.
I fully agree that it is a niche case anyway.

Mutex::<T>::lock does give &mut T access. You can see that in my code above, which you can run here, or in this shorter snippet:

let mutex = Mutex::new(0);
let mut guard = mutex.lock().unwrap();
let r: &mut i32 = &mut guard;

This is one of the main reasons for Mutex to exist: It provides unique/mutable (&mut T) access for a T that is shared across threads.

2 Likes

I'm personally having a slightly difficult time following who you're replying to, but perhaps that's cause I'm on mobile.

I'm guessing that you think you need a &mut Mutex in order to get mutable access to the value? @mbrubeck's last reply hopefully clears that up if it's the confusion.

Using Mutex::get_mut is simply a way to get at the underlying &mut T without actually locking. In order to do that, you need exclusive access to the Mutex, of course - that's what &mut self is there for. But, as mentioned, that's not actually how you canonically use the Mutex.

1 Like

If you're not going to share the Mutex across threads, then indeed it serves as a heavyweight unergonomic single threaded wrapper object. But, if you're reaching for Mutex for thread safety then you'll be sharing it across threads and thus need T: Send.

Oh yes, thank you!
I missed somehow the DerefMut on MutexGuard. Thanks @vitalyd and @mbrubeck now it is completely clear!

In other words everything that implements Sync usually also implements Send

I am afraid MutexGuard in std::sync - Rust (rust-lang.org) is a counter-example.

That is because the mutex needs to be unlocked on the thread that locked it (because it is using the OS primitives and the OS requires it etc.)

indeed.