捕获

闭包本质上很灵活,无需注解就能根据功能需求自动适应。这使得捕获可以灵活地适应不同场景,有时移动,有时借用。闭包可以通过以下方式捕获变量:

  • 通过引用:&T
  • 通过可变引用:&mut T
  • 通过值:T

闭包优先通过引用捕获变量,仅在必要时才使用更底部的的捕获方式。

fn main() {
    use std::mem;
    
    let color = String::from("green");

    // 打印 `color` 的闭包,立即借用(`&`)`color` 并
    // 将借用和闭包存储在 `print` 变量中。借用状态
    // 将持续到 `print` 最后一次使用。
    //
    // `println!` 只需要不可变引用参数,所以
    // 不会施加更多限制。
    let print = || println!("`color`: {}", color);

    // 使用借用调用闭包。
    print();

    // `color` 可以再次被不可变借用,因为闭包只持有
    // `color` 的不可变引用。
    let _reborrow = &color;
    print();

    // `print` 最后一次使用后,允许移动或重新借用
    let _color_moved = color;


    let mut count = 0;
    // 增加 `count` 的闭包可以接受 `&mut count` 或 `count`,
    // 但 `&mut count` 限制更少,所以选择它。立即
    // 借用 `count`。
    //
    // `inc` 需要 `mut` 因为内部存储了 `&mut`。因此,
    // 调用闭包会修改 `count`,这需要 `mut`。
    let mut inc = || {
        count += 1;
        println!("`count`: {}", count);
    };

    // 使用可变借用调用闭包。
    inc();

    // 闭包仍然可变借用 `count`,因为它稍后会被调用。
    // 尝试重新借用会导致错误。
    // let _reborrow = &count; 
    // ^ TODO:尝试取消注释这行。
    inc();

    // 闭包不再需要借用 `&mut count`。因此,
    // 可以在没有错误的情况下重新借用
    let _count_reborrowed = &mut count; 

    
    // 不可复制类型。
    let movable = Box::new(3);

    // `mem::drop` 需要 `T`,所以这里必须通过值获取。可复制类型
    // 会被复制到闭包中,原始值保持不变。
    // 不可复制类型必须移动,所以 `movable` 立即移动到闭包中。
    let consume = || {
        println!("`movable`: {:?}", movable);
        mem::drop(movable);
    };

    // `consume` 消耗了变量,所以只能调用一次。
    consume();
    // consume();
    // ^ TODO:尝试取消注释这行。
}

在竖线前使用 move 强制闭包获取捕获变量的所有权:

fn main() {
    // `Vec` 是非复制语义。
    let haystack = vec![1, 2, 3];

    let contains = move |needle| haystack.contains(needle);

    println!("{}", contains(&1));
    println!("{}", contains(&4));

    // println!("vec 中有 {} 个元素", haystack.len());
    // ^ 取消上面这行的注释会导致编译时错误
    // 因为借用检查器不允许在变量被移动后重用。
    
    
    // 从闭包签名中移除 `move` 将导致闭包
    // 不可变借用 _haystack_ 变量,因此 _haystack_ 仍可用,
    // 取消注释上面的行不会导致错误。
}

另请参阅:

Boxstd::mem::drop