Is it possible to share data with threads without any cloning?

2.5k views Asked by At

When I'm delegating work to threads I often have a piece of data that will outlive all of the threads, such as numbers in the following example:

use std::thread;

fn main() {
    let numbers = vec![1, 2, 3];

    let thread_a = thread::spawn(|| println!("{}", numbers.len()));
    let thread_b = thread::spawn(|| println!("{}", numbers.len()));

    thread_a.join().unwrap();
    thread_b.join().unwrap();
}

It's not modified anywhere, and because of the joins, it's guaranteed that the threads are done using it. However, Rust's borrow checker is not able to tell:

error[E0373]: closure may outlive the current function, but it borrows `numbers`, which is owned by the current function
 --> src/main.rs:6:34
  |
6 |     let thread_a = thread::spawn(|| println!("{}", numbers.len()));
  |                                  ^^                ------- `numbers` is borrowed here
  |                                  |
  |                                  may outlive borrowed value `numbers`
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:6:20
  |
6 |     let thread_a = thread::spawn(|| println!("{}", numbers.len()));
  |                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `numbers` (and any other referenced variables), use the `move` keyword
  |
6 |     let thread_a = thread::spawn(move || println!("{}", numbers.len()));
  |                                  ^^^^^^^

The solutions I've seen so far all involve cloning the piece of data (or cloning an Arc of the data). Is it possible to do it without any cloning, though?

1

There are 1 answers

1
DK. On BEST ANSWER

You might have the wrong idea: cloning an Arc is just incrementing a reference counter and making a copy of a pointer; it doesn't perform any additional allocation. Of course, creating the Arc involves an allocation, but then, you're already allocating in order to construct the Vec, so one additional fixed-size allocation isn't likely to hurt.

If all you really need is the length, you can just compute that outside the thread's closure and store it in a variable; a usize has no problems crossing a thread boundary.

The issue is that the compiler is unable to infer from the use of join() that a given thread is bound to a limited lifetime... it doesn't even try.

Before Rust 1.0, there was a thread::scoped constructor that allowed you to pass in non-'static references, but that had to be de-stabilised due to a memory safety issue. See How can I pass a reference to a stack variable to a thread? for alternatives.