Dear all, I experimented with threads from the standard library and stumbled upon a problem that appeared pretty strange to me. Consider the following program:
use std::sync::{Arc, Mutex};
use std::thread;
fn make_closure() -> impl FnMut() -> () {
let mut x = 0;
move || {
x += 1;
println!("Function executed #{}", x);
}
}
fn subfunc<F: FnMut() -> ()>(f: F) {
// Adding the following line allows the program to compile and run:
// let f = make_closure();
let arc_parent = Arc::new(Mutex::new(f));
let arc_child = arc_parent.clone();
let t = thread::spawn(move || {
let mut f = arc_child.lock().unwrap();
f();
});
t.join().unwrap();
let mut f = arc_parent.lock().unwrap();
f();
}
fn main() {
let f = make_closure();
subfunc(f);
}
Note that subfunc consumes its argument f. The program fails to compile with the following error message:
Compiling threadtest v0.1.0 (/usr/home/jbe/prj/rust/threadtest)
error[E0277]: `F` cannot be sent between threads safely
--> src/main.rs:17:13
|
17 | let t = thread::spawn(move || {
| ^^^^^^^^^^^^^ `F` cannot be sent between threads safely
|
= note: required because of the requirements on the impl of `Send` for `Mutex<F>`
= note: required because of the requirements on the impl of `Send` for `Arc<Mutex<F>>`
= note: required because it appears within the type `[closure@src/main.rs:17:27: 20:6]`
help: consider further restricting this bound
|
12 | fn subfunc<F: FnMut() -> () + Send>(f: F) {
| ^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
error: could not compile `threadtest`
To learn more, run the command again with --verbose.
If I change the signature of subfunc as suggested by the compiler (i.e. add + Send to the traits for type F), then the error message is as follows:
Compiling threadtest v0.1.0 (/usr/home/jbe/prj/rust/threadtest)
error[E0310]: the parameter type `F` may not live long enough
--> src/main.rs:17:13
|
12 | fn subfunc<F: FnMut() -> () + Send>(f: F) {
| -- help: consider adding an explicit lifetime bound...: `F: 'static +`
...
17 | let t = thread::spawn(move || {
| ^^^^^^^^^^^^^ ...so that the type `[closure@src/main.rs:17:27: 20:6]` will meet its required lifetime bounds
error: aborting due to previous error
For more information about this error, try `rustc --explain E0310`.
error: could not compile `threadtest`
To learn more, run the command again with --verbose.
However, if I uncomment the line let f = make_closure();, i.e. if i do not use the passed closure but create the closure within the function, then the program compiles and generates the following (desired) output:
Function executed #1
Function executed #2
I'm stuck here. Why does the program only work when I use the closure created within the function subfunc?
If I create the closure in main and pass it by moving (i.e. transferring ownership to subfunc, as in my first example above), I'd expect the program to work also. Where is the difference in lifetime guarantees? Maybe the compiler fails to notice something?
Any help is appreciated. I use rustc version 1.48.0.