Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

rust - Why is the value moved into the closure here rather than borrowed?

The Error Handling chapter of the Rust Book contains an example on how to use the combinators of Option and Result. A file is read and through application of a series of combinators the contents are parsed as an i32 and returned in a Result<i32, String>. Now, I got confused when I looked at the code. There, in one closure to an and_then a local String value is created an subsequently passed as a return value to another combinator.

Here is the code example:

use std::fs::File;
use std::io::Read;
use std::path::Path;

fn file_double<P: AsRef<Path>>(file_path: P) -> Result<i32, String> {
    File::open(file_path)
         .map_err(|err| err.to_string())
         .and_then(|mut file| {
              let mut contents = String::new(); // local value
              file.read_to_string(&mut contents)
                  .map_err(|err| err.to_string())
                  .map(|_| contents) // moved without 'move'
         })
         .and_then(|contents| {
              contents.trim().parse::<i32>()
                      .map_err(|err| err.to_string())
         })
         .map(|n| 2 * n)
}

fn main() {
    match file_double("foobar") {
        Ok(n) => println!("{}", n),
        Err(err) => println!("Error: {}", err),
    }
}

The value I am referring to is contents. It is created and later referenced in the map combinator applied to the std::io::Result<usize> return value of Read::read_to_string. The question: I thought that not marking the closure with move would borrow any referenced value by default, which would result in the borrow checker complaining, that contents does not live long enough. However, this code compiles just fine. That means, the String contents is moved into, and subequently out of, the closure. Why is this done without the explicit move?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I thought that not marking the closure with move would borrow any referenced value by default,

Not quite. The compiler does a bit of inspection on the code within the closure body and tracks how the closed-over variables are used.

When the compiler sees that a method is called on a variable, then it looks to see what type the receiver is (self, &self, &mut self). When a variable is used as a parameter, the compiler also tracks if it is by value, reference, or mutable reference. Whatever the most restrictive requirement is will be what is used by default.

Occasionally, this analysis is not complete enough — even though the variable is only used as a reference, we intend for the closure to own the variable. This usually occurs when returning a closure or handing it off to another thread.

In this case, the variable is returned from the closure, which must mean that it is used by value. Thus the variable will be moved into the closure automatically.


Occasionally the move keyword is too big of a hammer as it moves all of the referenced variables in. Sometimes you may want to just force one variable to be moved in but not others. In that case, the best solution I know of is to make an explicit reference and move the reference in:

fn main() {
    let a = 1;
    let b = 2;

    {
        let b = &b;
        needs_to_own_a(move || a_function(a, b));
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...