2

I'm new to Rust and looks like I'm seriously missing some concept here.

use std::thread;

fn main() {
    let mut children = vec![];

    //spawn threads
    for i in 0..10 {
        let c = thread::spawn(|| {
            println!("thread id is {}", i);
        });
        children.push(c);
    }

    for j in children {
        j.join().expect("thread joining issue");
    }
}

It fails with the error:

error[E0373]: closure may outlive the current function, but it borrows `i`, which is owned by the current function

Since the type of i is i32 , and there are no references involved shouldn't Rust copy the value instead of forcing to move ?

Acorn
  • 24,970
  • 5
  • 40
  • 69
manikawnth
  • 2,739
  • 1
  • 25
  • 39
  • Does this answer your question? [What are move semantics in Rust?](https://stackoverflow.com/questions/30288782/what-are-move-semantics-in-rust) – Stargateur Apr 20 '20 at 10:51
  • 1
    by default closure borrow, move will copy the i32 – Stargateur Apr 20 '20 at 10:52
  • Does this answer your question? [Does println! borrow or own the variable?](https://stackoverflow.com/questions/30450399/does-println-borrow-or-own-the-variable) – SCappella Apr 20 '20 at 11:05
  • 1
    @SCappella println borrows but when copied i32 into another variable `let k = i` inside the closure and printed it, i'm still getting the compilation error. Looks like it's the closure and not the println which is doing a primary borrow. But I'm checking for an authoritative source which mentions this. – manikawnth Apr 20 '20 at 11:10
  • I'd suggest editing to take out the irrelevant parts then. Don't say "there are no references involved" if your example *does* have references. [Here's a smaller example.](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f4e154136453f01fb940fd1f3d06e643) – SCappella Apr 20 '20 at 11:53
  • Here's the issue. It's working in normal closures, except when I'm spawning threads. – manikawnth Apr 20 '20 at 12:02

1 Answers1

7

The answer to your original question is that println! borrows its arguments. However, as you pointed out in the comments, even (apparently) moving the integer into the closure still causes a compile error.

For the purposes of this answer, we'll work with this code.

fn use_closure<F: FnOnce() + 'static>(_: F) {}

fn main() {
    let x: i32 = 0;
    use_closure(|| {
        let _y = x;
    });
}

(playground)

use_closure simulates what thread::spawn does in the original code: it consumes a closure whose type has to be 'static.

Attempting to compile this gives the error

error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
 --> src/main.rs:5:17
  |
5 |     use_closure(|| {
  |                 ^^ may outlive borrowed value `x`
6 |         let _y = x;
  |                  - `x` is borrowed here
  |
note: function requires argument type to outlive `'static`
 --> src/main.rs:5:5
  |
5 | /     use_closure(|| {
6 | |         let _y = x;
7 | |     });
  | |______^
help: to force the closure to take ownership of `x` (and any other referenced variables), use the `move` keyword
  |
5 |     use_closure(move || {
  |                 ^^^^^^^

Wait, what?

6 |         let _y = x;
  |                  - `x` is borrowed here

Why is x borrowed there? Shouldn't it be a copy? The answer lies in "capture modes" for closures. From the documentation

The compiler prefers to capture a closed-over variable by immutable borrow, followed by unique immutable borrow (see below), by mutable borrow, and finally by move. It will pick the first choice of these that allows the closure to compile. The choice is made only with regards to the contents of the closure expression; the compiler does not take into account surrounding code, such as the lifetimes of involved variables.

Precisely because x has a Copy type, the closure itself can compile with a mere immutable borrow. Given an immutable borrow of x (call it bor), we can do our assignment to _y with _y = *bor. This isn't a "move out of data behind a reference" because this is a copy instead of a move.

However, since the closure borrows a local variable, its type won't be 'static, so it won't be usable in use_closure or thread::spawn.

Trying the same code with a type that isn't Copy, it actually works perfectly, since the closure is forced to capture x by moving it.

fn use_closure<F: FnOnce() + 'static>(_: F) {}

fn main() {
    let x: Vec<i32> = vec![];
    use_closure(|| {
        let _y = x;
    });
}

(playground)


Of course, as you already know, the solution is to use the move keyword in front of the closure. This forces all captured variables to be moved into the closure. Since the variable won't be borrowed, the closure will have a static type and will be able to be used in use_closure or thread::spawn.

fn use_closure<F: FnOnce() + 'static>(_: F) {}

fn main() {
    let x: i32 = 0;
    use_closure(move || {
        let _y = x;
    });
}

(playground)

SCappella
  • 9,534
  • 1
  • 26
  • 35