The following code refuses to compile unless I put the sender field into a Mutex.

I am not exactly sure why? That field is not being used within the spawned thread. Also the receiver variable is passed through quite happily as a parameter without needing to be Mutexed… If I put the receiver into the struct rather as a parameter, then that will need to be within a Mutex too.

So there is something about it being a part of the struct that means it has to be locked. Any ideas?

This is due to requirements on thread::spawn(), mpsc::Sender, and Arc. Let’s break them down:

spawn() wants the closure F given to it to be F: Send + 'static (the Send part is important)

You’re capturing an Arc in the closure given to spawn(), so it needs to be Send due to #1

Arc<T> is Send when T: Send + Sync. T here is your Amazing struct.

mpsc::Sender is explicitly !Sync, which makes your Amazing not Sync - this fails the requirements.

Since Sender is Send, a Mutex<T> is Sync if T: Send - this means putting the Sender inside a mutex makes your AmazingSync, and satisfies the requirements.

The short of it is that your spawn() closure has an Arc<Amazing> in it because you move'd the a clone into it. Even though you then proceed to just use stuff and not sender, the compiler doesn’t look at it in those terms. As far as it’s concerned, your closure contains a Arc<Amazing> and that needs to satisfy the obligations/requirements above.

Normally, most types are Send and Sync and it’s completely fine to share them across threads via an Arc. mpsc::Sender, however, is not Sync. Instead, it’s Clone + Send. To have multiple producers across threads sending to a single receiver, you would clone() the sender and then move (ie Send) it to a thread; each thread would have its own Sender value (all of them operating on the same underlying channel, of course).

mongus:

It makes it much simpler pulling only what you need into the closure.

Besides being simpler and easier to grok mentally, you’ll also find that splitting things fairly granularly will help you with Rust code in general, even outside of multithreading. For instance, taking borrows of individual fields of a struct, rather than the entire struct, will please the borrow checker more frequently, and you in turn .

Interesting, thanks. Yes that does make sense… You need to be more explicit about what is happening to all the individual components of your data. I can see that I am going to have to rethink the way I structure my programs.