Share data among threads?

Can I share immutable data among threads?

use std::thread::spawn;

fn main() {
    let shared = String::from("cake");
    let tasks = vec![1, 2, 3];

    let mut threads = Vec::new();

    for task in tasks {
        threads.push(spawn(|| {
            println!("This is task {} and we are sharing one single {}", task, shared);
        }));
    }

    for t in threads {
        t.join().unwrap();
    }
}

(Playground)

error[E0373]: closure may outlive the current function, but it borrows `task`, which is owned by the current function
  --> src/main.rs:12:28
   |
12 |         threads.push(spawn(|| {
   |                            ^^ may outlive borrowed value `task`
13 |             println!("This is task {} and we are sharing one single {}", task, shared);
   |                                                                          ---- `task` is borrowed here
   |
note: function requires argument type to outlive `'static`

But if I add move then the closure gobbles up shared:

error[E0382]: use of moved value: `shared`
  --> src/main.rs:12:28
   |
5  |     let shared = String::from("cake");
   |         ------ move occurs because `shared` has type `std::string::String`, which does not implement the `Copy` trait
...
12 |         threads.push(spawn(move || {
   |                            ^^^^^^^ value moved into closure here, in previous iteration of loop
13 |             println!("This is task {} and we are sharing one single {}", task, shared);
   |                                                                                ------ use occurs due to use in closure

Looks like a job for leaking memory. Rust doesn't know that the first thread won't exit before the second thread completes leading to a use after free bug. You can leak memory to get a static reference to work around this.

In order for shared to live long enough, you can wrap it in an Arc

fn main() {
    let shared = Arc::new(String::from("cake"));
    let tasks = vec![1, 2, 3];

    let mut threads = Vec::new();

    for task in tasks {
        let shared = shared.clone();
        threads.push(spawn(move || {
            println!("This is task {} and we are sharing one single {}", task, shared);
        }));
    }

    for t in threads {
        t.join().unwrap();
    }
}
1 Like

Alternatively, you can use thread scopes from crossbeam, which allow child threads to borrow local variables:

fn main() {
    let cake = String::from("cake");

    crossbeam::scope(|s| {
        for i in 1..=3 {
            let cake = &cake;
            s.spawn(move |_| {
                println!("This is task {} and we are sharing one single {}", i, cake);
            });
        }
    }).expect("threads did not complete successfully");
}

(playground)

This method is more efficient (and possibly more intuitive) because it keeps main as the owner of the shared variable and does not require reference counting. Note that all child threads are automatically joined at the end of the scope to prevent lifetime errors, which is very handy in this case.

2 Likes

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.