探索 Rust 闭包及其捕获行为

RustRustBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在本实验中,我们将探索 Rust 中的闭包及其捕获行为,这使得闭包能够根据其需求按引用、可变引用或值来捕获变量。

注意:如果实验未指定文件名,你可以使用任何你想要的文件名。例如,你可以使用 main.rs,并通过 rustc main.rs &&./main 来编译和运行它。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/AdvancedTopicsGroup(["Advanced Topics"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/MemorySafetyandManagementGroup(["Memory Safety and Management"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/ErrorHandlingandDebuggingGroup(["Error Handling and Debugging"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/MemorySafetyandManagementGroup -.-> rust/lifetime_specifiers("Lifetime Specifiers") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") rust/ErrorHandlingandDebuggingGroup -.-> rust/error_propagation("Error Propagation") rust/AdvancedTopicsGroup -.-> rust/operator_overloading("Traits for Operator Overloading") subgraph Lab Skills rust/variable_declarations -.-> lab-99323{{"探索 Rust 闭包及其捕获行为"}} rust/mutable_variables -.-> lab-99323{{"探索 Rust 闭包及其捕获行为"}} rust/function_syntax -.-> lab-99323{{"探索 Rust 闭包及其捕获行为"}} rust/expressions_statements -.-> lab-99323{{"探索 Rust 闭包及其捕获行为"}} rust/lifetime_specifiers -.-> lab-99323{{"探索 Rust 闭包及其捕获行为"}} rust/method_syntax -.-> lab-99323{{"探索 Rust 闭包及其捕获行为"}} rust/error_propagation -.-> lab-99323{{"探索 Rust 闭包及其捕获行为"}} rust/operator_overloading -.-> lab-99323{{"探索 Rust 闭包及其捕获行为"}} end

捕获

闭包本质上具有灵活性,会根据功能需求来使闭包正常工作而无需注释。这使得捕获能够灵活地适应不同用例,有时进行移动,有时进行借用。闭包可以按以下方式捕获变量:

  • 按引用:&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`。因此,调用闭包会使闭包发生变异,这就需要一个 `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!("There're {} elements in vec", haystack.len());
    // ^ 取消注释上面这一行会导致编译时错误
    // 因为借用检查器不允许在变量被移动后再次使用它。

    // 从闭包签名中移除 `move` 会导致闭包不可变地借用 `_haystack_` 变量,因此 `_haystack_` 仍然可用,取消注释上面那一行不会导致错误。
}

总结

恭喜你!你已经完成了“捕获”实验。你可以在 LabEx 中练习更多实验来提升你的技能。