Issue with borrowing while spawning thread inside other thread

1.1k Views Asked by At

I have read the following question:

How can I run a set of functions on a recurring interval without running the same function at the same time using only the standard Rust library?

and elaborated some more complex tests. The following code add a &str parameter for the functions and it works:

use std::{
    thread,
    time::{Duration, Instant},
};

fn main() {
    let scheduler = thread::spawn(|| {
        let wait_time = Duration::from_millis(500);

        let one: &str = "Alpha";
        let two: &str = "Beta";

        // Make this an infinite loop
        // Or some control path to exit the loop
        for _ in 0..5 {
            let start = Instant::now();
            eprintln!("Scheduler starting at {:?}", start);

            let thread_a = thread::spawn(move || { a(&one) });
            let thread_b = thread::spawn(move || { b(&two) });

            thread_a.join().expect("Thread A panicked");
            thread_b.join().expect("Thread B panicked");

            let runtime = start.elapsed();

            if let Some(remaining) = wait_time.checked_sub(runtime) {
                eprintln!(
                    "schedule slice has time left over; sleeping for {:?}",
                    remaining
                );
                thread::sleep(remaining);
            }
        }
    });

    scheduler.join().expect("Scheduler panicked");
}

fn a(a: &str) {
    eprintln!("{}", a);
    thread::sleep(Duration::from_millis(100))
}
fn b(b: &str) {
    eprintln!("{}", b);
    thread::sleep(Duration::from_millis(200))
}

My understanding is that this works because Copy Trait is implemented for str. Now consider the following example:

use std::{
    thread,
    time::{Duration, Instant},
};

fn main() {
    let scheduler = thread::spawn(|| {
        let wait_time = Duration::from_millis(500);

        let one: String = String::from("Alpha");
        let two: String = String::from("Beta");

        // Make this an infinite loop
        // Or some control path to exit the loop
        for _ in 0..5 {
            let start = Instant::now();
            eprintln!("Scheduler starting at {:?}", start);

            let thread_a = thread::spawn(move || { a(&one) });
            let thread_b = thread::spawn(move || { b(&two) });

            thread_a.join().expect("Thread A panicked");
            thread_b.join().expect("Thread B panicked");

            let runtime = start.elapsed();

            if let Some(remaining) = wait_time.checked_sub(runtime) {
                eprintln!(
                    "schedule slice has time left over; sleeping for {:?}",
                    remaining
                );
                thread::sleep(remaining);
            }
        }
    });

    scheduler.join().expect("Scheduler panicked");
}

fn a(a: &str) {
    eprintln!("{}", a);
    thread::sleep(Duration::from_millis(100))
}
fn b(b: &str) {
    eprintln!("{}", b);
    thread::sleep(Duration::from_millis(200))
}

I am getting this at compile time:

error[E0382]: use of moved value: `one`
  --> src\main.rs:19:42
   |
10 |         let one: String = String::from("Alpha");
   |             --- move occurs because `one` has type `String`, which does not implement the `Copy` trait
...
19 |             let thread_a = thread::spawn(move || { a(&one) });
   |                                          ^^^^^^^      --- use occurs due to use in closure
   |                                          |
   |                                          value moved into closure here, in previous iteration of loop

error[E0382]: use of moved value: `two`
  --> src\main.rs:20:42
   |
11 |         let two: String = String::from("Beta");
   |             --- move occurs because `two` has type `String`, which does not implement the `Copy` trait
...
20 |             let thread_b = thread::spawn(move || { b(&two) });
   |                                          ^^^^^^^      --- use occurs due to use in closure
   |                                          |
   |                                          value moved into closure here, in previous iteration of loop

error: aborting due to 2 previous errors

EDIT1

It seems it can be solved with the use of .clone() But now consider the following code:

use std::{
    thread,
    time::{Duration, Instant},
};

fn main() {

    let one: String = String::from("Alpha");
    let two: String = String::from("Beta");

    let scheduler = thread::spawn(|| {
        let wait_time = Duration::from_millis(500);

        // Make this an infinite loop
        // Or some control path to exit the loop
        for _ in 0..5 {
            let start = Instant::now();
            eprintln!("Scheduler starting at {:?}", start);

            let one = one.clone();
            let two = two.clone();

            let thread_a = thread::spawn(move || { a(&one) });
            let thread_b = thread::spawn(move || { b(&two) });

            thread_a.join().expect("Thread A panicked");
            thread_b.join().expect("Thread B panicked");

            let runtime = start.elapsed();

            if let Some(remaining) = wait_time.checked_sub(runtime) {
                eprintln!(
                    "schedule slice has time left over; sleeping for {:?}",
                    remaining
                );
                thread::sleep(remaining);
            }
        }
    });

    scheduler.join().expect("Scheduler panicked");
}

fn a(a: &str) {
    eprintln!("{}", a);
    thread::sleep(Duration::from_millis(100))
}
fn b(b: &str) {
    eprintln!("{}", b);
    thread::sleep(Duration::from_millis(200))
}

I am now getting different error codes:

error[E0373]: closure may outlive the current function, but it borrows `two`, which is owned by the current function
  --> src\main.rs:11:35
   |
11 |     let scheduler = thread::spawn(|| {
   |                                   ^^ may outlive borrowed value `two`
...
21 |             let two = two.clone();
   |                       --- `two` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src\main.rs:11:21
   |
11 |       let scheduler = thread::spawn(|| {
   |  _____________________^
12 | |         let wait_time = Duration::from_millis(500);
13 | |
14 | |         // Make this an infinite loop
...  |
38 | |         }
39 | |     });
   | |______^
help: to force the closure to take ownership of `two` (and any other referenced variables), use the `move` keyword
   |
11 |     let scheduler = thread::spawn(move || {
   |                                   ^^^^^^^

error[E0373]: closure may outlive the current function, but it borrows `one`, which is owned by the current function
  --> src\main.rs:11:35
   |
11 |     let scheduler = thread::spawn(|| {
   |                                   ^^ may outlive borrowed value `one`
...
20 |             let one = one.clone();
   |                       --- `one` is borrowed here
   |
note: function requires argument type to outlive `'static`
  --> src\main.rs:11:21
   |
11 |       let scheduler = thread::spawn(|| {
   |  _____________________^
12 | |         let wait_time = Duration::from_millis(500);
13 | |
14 | |         // Make this an infinite loop
...  |
38 | |         }
39 | |     });
   | |______^
help: to force the closure to take ownership of `one` (and any other referenced variables), use the `move` keyword
   |
11 |     let scheduler = thread::spawn(move || {
   |                                   ^^^^^^^

error: aborting due to 2 previous errorsù
2

There are 2 best solutions below

2
On

For brevity I'll only mention one, but the same applies to two. The issue with thread::spawn(move || { a(&one) }) is that one is moved into the closure, which then results in the compile error, as one is no longer available for the next iteration.

Borrowing &one up front won't work either, because the thread borrowing one can outlive the outer thread. To get it working you can clone one (and two) before spawning the threads.

let one = one.clone();
let two = two.clone();

let thread_a = thread::spawn(move || a(&one));
let thread_b = thread::spawn(move || b(&two));

Alternatively, if you really want to borrow it, and not clone it. Then you can use, e.g. crossbeam and a scope for spawning threads. See also "How can I pass a reference to a stack variable to a thread?".

...

let one: String = String::from("Alpha");
let two: String = String::from("Beta");

let one = &one;
let two = &two;

crossbeam::scope(|scope| {
    // Make this an infinite loop
    // Or some control path to exit the loop
    for _ in 0..5 {
        let start = Instant::now();
        eprintln!("Scheduler starting at {:?}", start);

        let thread_a = scope.spawn(move |_| a(&one));
        let thread_b = scope.spawn(move |_| b(&two));

        thread_a.join().expect("Thread A panicked");
        thread_b.join().expect("Thread B panicked");

        let runtime = start.elapsed();

        if let Some(remaining) = wait_time.checked_sub(runtime) {
            eprintln!(
                "schedule slice has time left over; sleeping for {:?}",
                remaining
            );
            thread::sleep(remaining);
        }
    }
})
.unwrap();
0
On

If you're sharing non-static immutable data among multiple threads, use Arc. That's what it's for.