alloc::sync::Arc - reference counting stymied by closure?

Hello Rustaceans,

I'm trying to make subsequent threads to use clones of the same tx to send messages to the first thread. So I wrapped tx in an Arc. My understanding is that this should move tx to the heap, and there it should live until all references to it are gone.

But the compiler complains that tx doesn't live long enough. Isn't that the exact problem that the Arc is supposed to solve? Code below, and thanks in advance for any insights.

use std::sync::Arc;
use std::sync::Mutex;
use std::thread;

pub fn t1(rx1: Receiver<String>, tx2: Arc<Mutex<Sender<String>>>) {
    println!("1");
}

pub fn t2(rx2: Receiver<String>, tx1: Arc<Mutex<Sender<String>>>) {
    println!("2");
}

pub fn main() {

    let (tx1, rx1) = std::sync::mpsc::channel();
    let tx1 = Arc::new(Mutex::new(tx1));
    let (tx2, rx2) = std::sync::mpsc::channel();
    let tx2 = Arc::new(Mutex::new(tx2));

    let j1 = thread::spawn( || {
        t1(rx1, tx2.clone());
    });
    let j2 = thread::spawn( ||  {
        let f = t2(rx2, tx1.clone());
        });

    j1.join();
    j2.join();
}```

Please surround your code with three backticks (```) to render it as code.

You can clone the sender of an mpsc channel directly. Don't wrap it in an Arc or Mutex.

1 Like

If you write tx2.clone() inside a closure, you're saying that the closure should capture a reference to tx2 and clone it when called. This is fine normally, but a problem for passing the closure to thread::spawn for two reasons:

  1. while Sender itself is fine to move between threads, &Sender is not (that is, Sender is Send but not Sync)
  2. Threads can outlive the thread that spawned them. A closure passed to thread::spawn must not capture references to local variables in its environment because those local variables can be destroyed by the first thread without the second thread knowing about it.

Instead, clone tx2 outside the closure so when you use it inside Rust will know to move it.

Fixed up example (with some other changes to make it compile without warnings):

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

pub fn t1(_rx1: Receiver<()>, _tx2: Sender<()>) {
    println!("1");
}

pub fn t2(_rx2: Receiver<()>, _tx1: Sender<()>) {
    println!("2");
}

pub fn main() {
    let (tx1, rx1) = channel();
    let (tx2, rx2) = channel();

    let tx2_cloned = tx2.clone();
    let j1 = thread::spawn(|| {
        t1(rx1, tx2_cloned);
    });
    
    let tx1_cloned = tx1.clone();
    let j2 = thread::spawn(|| {
        let _f = t2(rx2, tx1_cloned);
    });

    j1.join().unwrap();
    j2.join().unwrap();
}
1 Like

Thank you, @trentj ! Seems obvious now that I think about it.

Thanks for the reply, @alice!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.