4

I have a struct which holds an Arc<Receiver<f32>> and I'm trying to add a method which takes ownership of self, and moves the ownership into a new thread and starts it. However, I'm getting the error

error[E0277]: the trait bound `std::sync::mpsc::Receiver<f32>: std::marker::Sync` is not satisfied
  --> src/main.rs:19:9
   |
19 |         thread::spawn(move || {
   |         ^^^^^^^^^^^^^ `std::sync::mpsc::Receiver<f32>` cannot be shared between threads safely
   |
   = help: the trait `std::marker::Sync` is not implemented for `std::sync::mpsc::Receiver<f32>`
   = note: required because of the requirements on the impl of `std::marker::Send` for `std::sync::Arc<std::sync::mpsc::Receiver<f32>>`
   = note: required because it appears within the type `Foo`
   = note: required because it appears within the type `[closure@src/main.rs:19:23: 22:10 self:Foo]`
   = note: required by `std::thread::spawn`

If I change the struct to hold an Arc<i32> instead, or just a Receiver<f32>, it compiles, but not with a Arc<Receiver<f32>>. How does this work? The error doesn't make sense to me as I'm not trying to share it between threads (I'm moving it, not cloning it).

Here is the full code:

use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;
use std::thread;

pub struct Foo {
    receiver: Arc<Receiver<f32>>,
}

impl Foo {
    pub fn new() -> (Foo, Sender<f32>) {
        let (sender, receiver) = channel::<f32>();
        let sink = Foo {
            receiver: Arc::new(receiver),
        };
        (sink, sender)
    }

    pub fn run_thread(self) -> thread::JoinHandle<()> {
        thread::spawn(move || {
            println!("Thread spawned by 'run_thread'");
            self.run(); // <- This line gives the error
        })
    }

    fn run(mut self) {
        println!("Executing 'run'")
    }
}

fn main() {
    let (example, sender) = Foo::new();
    let handle = example.run_thread();
    handle.join();
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
André T.
  • 328
  • 3
  • 13
  • Does this answer your question? [Rust mpsc::Sender cannot be shared between threads?](https://stackoverflow.com/questions/40384274/rust-mpscsender-cannot-be-shared-between-threads) – Aunmag Dec 18 '20 at 09:55

1 Answers1

8

How does this work?

Let's check the requirements of thread::spawn again:

pub fn spawn<F, T>(f: F) -> JoinHandle<T> 
where
    F: FnOnce() -> T,
    F: Send + 'static,   // <-- this line is important for us
    T: Send + 'static, 

Since Foo contains an Arc<Receiver<_>>, let's check if and how Arc implements Send:

impl<T> Send for Arc<T> 
where
    T: Send + Sync + ?Sized,

So Arc<T> implements Send if T implements Send and Sync. And while Receiver implements Send, it does not implement Sync.

So why does Arc have such strong requirements for T? T also has to implement Send because Arc can act like a container; if you could just hide something that doesn't implement Send in an Arc, send it to another thread and unpack it there... bad things would happen. The interesting part is to see why T also has to implement Sync, which is apparently also the part you are struggling with:

The error doesn't make sense to me as I'm not trying to share it between threads (I'm moving it, not cloning it).

The compiler can't know that the Arc in Foo is in fact not shared. Consider if you would add a #[derive(Clone)] to Foo later (which is possible without a problem):

fn main() {
    let (example, sender) = Foo::new();
    let clone = example.clone();
    let handle = example.run_thread();
    clone.run();
    // oopsie, now the same `Receiver` is used from two threads!

    handle.join();
}

In the example above there is only one Receiver which is shared between threads. And this is no good, since Receiver does not implement Sync!

To me this code raises the question: why the Arc in the first place? As you noticed, without the Arc, it works without a problem: you clearly state that Foo is the only owner of the Receiver. And if you are "not trying to share [the Receiver]" anyway, there is no point in having multiple owners.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • Thanks, it makes a lot more sense now! The reason for wanting it in an Arc is because I want to use it from within a closure which is called from C-code later on (PortAudio), but I have a feeling I should probably think of a better way to do it. – André T. Apr 30 '18 at 15:02
  • @AndréT. If you have multiple consumers of the channel, you should probably use a library that has mpmc channels, like [`crossbeam-channel`](https://crates.io/crates/crossbeam-channel). Then, your `Receiver` is `Sync`. Or you could ask on `/r/rust` or in the users forum what others would recommend in your situation :) – Lukas Kalbertodt Apr 30 '18 at 15:21