为什么这里将值移入闭包而不是借用?

9
《Rust Book》的错误处理章节中有一个关于如何使用OptionResult组合子的示例。文件被读取并通过一系列组合子的应用将其内容解析为i32,并以Result<i32,String>返回。
当我查看代码时感到困惑。在一个闭包中,一个本地String值被创建,随后作为返回值传递给另一个组合器。以下是代码示例:
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),
    }
}

我所指的值是contents。它是在应用于Read::read_to_stringstd::io::Result<usize>返回值的map组合器中创建和引用的。 问题是:我认为使用move标记闭包会默认借用任何引用的值,这将导致借用检查器抱怨contents的生命周期不够长。然而,这段代码编译得非常好。这意味着String contents被移动到闭包中,随后又被移出。为什么可以不使用显式的move
1个回答

8
我曾以为,如果在闭包中不标记move,则默认会借用任何引用的值。
但实际上,编译器会检查闭包体内的代码,并跟踪封闭变量的使用方式。当编译器发现变量上调用了一个方法时,它就会查看接收者的类型 (self, &self, &mut self)。当一个变量被用作参数时,编译器还会跟踪它是按值、引用还是可变引用传递的。最严格的要求将成为默认值。
偶尔,这种分析不够完整 - 即使变量只被用作引用,我们也打算让闭包拥有该变量。通常,在返回闭包或将其移交给另一个线程时会出现这种情况。
在这种情况下,变量从闭包中返回,这意味着它是按值使用的。因此,该变量将自动移动到闭包中。
有时,“move”关键字过于强大,它会将所有引用的变量都移入闭包中。有时您可能只想强制移入一个变量,而不是其他变量。在这种情况下,我知道的最佳解决方案是创建一个显式的引用并将引用移入。
fn main() {
    let a = 1;
    let b = 2;

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

那个闭包传递一个usize作为参数,并返回一个String。因此,String值被移出闭包环境,所以它必须首先被移入。这基本上是编译器在这里的推理吗? - jtepe
@JonasTepe听起来大概是对的——如果在闭包内部使用了按值传递的方式调用某个函数/方法,它也会被移动进去。 - Shepmaster

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接