Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the move keyword not always needed even when the closure takes ownership of a value? [duplicate]

Tags:

rust

While reading the last chapter of the Rust book, I couldn't help but notice that move was not used within a closure:

fn main() {
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    for stream in listener.incoming() {
        let stream = stream.unwrap();

        // move not used here
        thread::spawn(|| {
            handle_connection(stream);
        });
    }
}

Here is the function signature of handle_connection:

fn handle_connection(mut stream: TcpStream) {}

Why isn't move used here? What would cause move to be required from within the closure?

like image 393
g.delgado Avatar asked Aug 05 '18 22:08

g.delgado


People also ask

What does the move keyword do in Rust?

Keyword move move converts any variables captured by reference or mutable reference to variables captured by value. move is often used when threads are involved. move is also valid before an async block.

What is a closure rust?

Rust's closures are anonymous functions you can save in a variable or pass as arguments to other functions. You can create the closure in one place and then call the closure elsewhere to evaluate it in a different context. Unlike functions, closures can capture values from the scope in which they're defined.


1 Answers

Rust can tell when a closure uses a value from the environment in a way that requires a move. Like calling a function that takes the argument by value (your handle_connection case):

let s = String::from("hi");
let c = || drop(s);  // <-- `drop()` takes its argument by value
                     //      Thus, the compiler knows `c` is a move closure

Or if the closure returns the object by value:

let s = String::from("hi");
let c = || s;  // <-- `s` is returned (FnOnce() -> String)
               //      Thus, the compiler knows `c` is a move closure

So usually, you don't have to annotate the move keyword to explicitly tell the compiler.

However, if the closure uses the value from the environment only via references, the compiler assumes that moving that variable into the closure is not necessary. But it might still be necessary for another reason: lifetimes. Example:

fn get_printer(s: String) -> Box<Fn()> {
    Box::new(|| println!("{}", s))
}

In this case, the compiler only sees that s is used in read only fashion via reference (println doesn't consume its arguments). Thus the compiler doesn't make the closure a move closure. But this results in a lifetime error, because s now lives in the stackframe of get_printer and the closure outlives that stackframe. So in this case, you have to force the compiler to move the environment into the closure by adding move:

fn get_printer(s: String) -> Box<Fn()> {
    Box::new(move || println!("{}", s))
}
like image 157
Lukas Kalbertodt Avatar answered Nov 15 '22 09:11

Lukas Kalbertodt