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.