A more functioning example would help, indeed. Here’s your code a bit polished up. (I wrote this before you updated your post.)
use std::sync::mpsc;
use std::sync::mpsc::Receiver;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
type ArcReceiver = Arc<Mutex<Receiver<i32>>>;
fn a_function(_arc_receiver: ArcReceiver) {}
fn main() {
let (_sender, receiver): (std::sync::mpsc::Sender<i32>, std::sync::mpsc::Receiver<i32>) =
mpsc::channel();
let sb: ArcReceiver = Arc::new(Mutex::new(receiver));
let mut threads: Vec<std::thread::JoinHandle<()>> = Vec::with_capacity(10);
for _ in 0..10 {
/* this compiles */
let c: ArcReceiver = Arc::clone(&sb); // WHY DO I HAVE TO CREATE the temporary c ???
threads.push(thread::spawn(|| a_function(c)));
/* this doesn't */
// threads.push(thread::spawn(|| a_function(Arc::clone(&sb))));
}
for thread in threads {
thread.join().unwrap();
}
println!("Hello, world!");
}
Including a link to the Rust playground is also a good idea.
It’s also always useful to include the actual error message, so if line 22 is uncommented, we get:
Compiling playground v0.0.1 (/playground)
error[E0373]: closure may outlive the current function, but it borrows `sb`, which is owned by the current function
--> src/main.rs:22:36
|
22 | threads.push(thread::spawn(|| a_function(Arc::clone(&sb))));
| ^^ -- `sb` is borrowed here
| |
| may outlive borrowed value `sb`
|
note: function requires argument type to outlive `'static`
--> src/main.rs:22:22
|
22 | threads.push(thread::spawn(|| a_function(Arc::clone(&sb))));
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `sb` (and any other referenced variables), use the `move` keyword
|
22 | threads.push(thread::spawn(move || a_function(Arc::clone(&sb))));
| ^^^^^^^
error: aborting due to previous error
For more information about this error, try `rustc --explain E0373`.
error: could not compile `playground`.
To learn more, run the command again with --verbose.
Note that the suggested possible fix of adding move
does indeed not work either.
The problem is that, as @2e71828 tried to explain, the cloning happens in a different thread between the two versions. First, you need a bit of a mental modal of what a closure, like your expression || a_function(Arc::clone(&sb))
, actually is. It gets compiled into an anonymous datatype that “captures” (i.e. contains the values of) all the local variables that the code you wrote for it needs to be executed. And it implements the appropriate Fn
traits so that calling the closure runs the code you wrote there using those values for the local variables. There’s two steps to this process, one is packaging up all the local variables into the closure data type and the second one is executing the closure.
Crucially, the execution step can happen much later and in the case of thread::spawn
the execution will happen in a different thread.
Accessing variables can be done in different ways. When you pass a variable directly to something like a function, its contents get moved. If a variable x
is used in an expression &x
or &mut x
(this can also happen implicitly when a &self
or &mut self
method is called on x
) then the value is only borrowed (immutably or mutably, respectively). When compiling a closure, the compiler will determine how a local variable is used. In the case of || a_function(c)
, the value of the variable c
needs to be moved when executing the closure. In the case of || a_function(Arc::clone(&sb))
, the value of the variable sb
needs to be (immutably) referenced.
In order to be able to move or reference the respective values at execution time, there needs to happen different things when a closure is build (i.e. when the captured local variables are packaged up into the anonymous data type). In order to support moving at execution time, the original variable needs to be moved into the closure at closure-building time as well. For a value that only needs to be referenced at execution time, you just need to have a reference created at closure-build time as well. This reference is stored in the closure and still references the original variable. This is really helpful for closures in functions like Iterator::map
or Option::unwrap_or_else
where you immediately execute the closure in the same scope.
But it doesn’t support the closure being move out of the scope of the referenced variable. Returning a closure from the function it is created in, or sending it to another thread are both common examples where the closure (and all the values or references it contains) are leaving the current scope. Trying to have a local variable captured by reference in such a closure will trigger lifetime related errors like the one above.
closure may outlive the current function, but it borrows `sb`, which is owned by the current function
Finally, there is the move
keyword. It will change the closure-building/packaging process so that every captured variable is packaged up directly (and thus the original variable is moved) no matter what kind of usage the compiler detects for the closure’s execution time. In practice, you can actually always use std::thread::spawn(move || ...)
with the move
keyword, since it won’t make a difference in the cases that compiled without it, but it will make code work in other cases that didn’t work without it. Once you accepted that thread::spawn
needs to move the value out of every variable it captures, you’ll understand that you cannot possibly directly mention your variable sb
inside of a closure in a loop since you’d need to move out of sb
multiple times for that to work (and sb
’s type is not Copy). Rust also does not have any special syntax for something like “capturing by clone
”, so the only way is to use a local variable, as you did, to first clone the value and then capture move that clone into the closure.