Skip to content
📖 Auto 版 Rust Book本书目录

函数式语言特性:闭包与迭代器 ​

Auto 的设计借鉴了许多现有语言和技术,其中一个重要的影响来源是_函数式编程_。函数式编程风格通常包括将函数作为值传递、从其他函数返回、赋值给变量以供后续执行等。

本章将介绍:

  • 闭包,可以存储在变量中的匿名函数
  • 迭代器,处理一系列元素的方式
  • 如何用闭包和迭代器改进第 12 章的 I/O 项目
  • 闭包和迭代器的性能

闭包 ​

闭包是可以保存在变量中或作为参数传递给其他函数的匿名函数。与普通函数不同,闭包可以捕获定义它的作用域中的值。

闭包语法 ​

Auto 闭包使用 (params) => expr 语法:

auto
let add = (a int, b int) => a + b
let result = add(5, 3)
print(result)
rust
let add = |a: i32, b: i32| a + b;
let result = add(5, 3);
println!("{}", result);

这等价于 Rust 的 |params| expr 语法。闭包也可以有显式返回类型:

auto
let expensive_closure = (num int) -> int {
    print("calculating slowly...")
    num
}
rust
let expensive_closure = |num: i32| -> i32 {
    println!("calculating slowly...");
    num
};

捕获环境 ​

闭包可以从其环境中捕获值。以下是一个使用 unwrap_or_else 的示例,其中闭包捕获了 self:

闭包从环境中捕获 `self`

闭包 (self) => self.most_stocked() 捕获了 self 的不可变引用并调用 most_stocked()。?T(Option)上的 unwrap_or_else 方法仅在值为 None 时 才调用闭包。

类型推断 ​

闭包通常不需要类型标注——编译器会从使用中推断:

闭包类型推断

编译器从第一次调用推断 x 的类型为 String。一旦推断完成,闭包的类型就被 锁定——用不同的类型调用它会导致编译错误。

捕获引用或转移所有权 ​

闭包可以通过三种方式捕获值,对应函数接收参数的三种方式:

  1. 不可变引用 — 闭包只读取值
  2. 可变引用 — 闭包修改值
  3. 所有权 — 闭包获取所有权(Rust 中使用 move)
带显式类型标注的闭包

下面是一个捕获不可变引用的闭包:

捕获不可变引用的闭包

闭包 only_borrows 通过不可变引用捕获 list。原始 list 在闭包调用前、 调用中和调用后都可以访问。

以及一个捕获可变引用的闭包:

捕获可变引用的闭包

当 borrows_mutably 捕获可变引用时,在闭包定义和调用之间不允许对 list 的其他访问。

Fn 特征 ​

与 Rust 一样,闭包实现一个或多个 Fn 特征:

  • FnOnce — 只能调用一次(移出捕获的值)
  • FnMut — 可以多次调用,可能修改捕获的值
  • Fn — 可以多次调用且不修改捕获的值

这些特征在将闭包传递给函数或存储在类型中时很重要。

迭代器 ​

迭代器提供了一种处理元素序列的方式。在 Auto 中,for 循环是迭代的主要方式:

auto
for item in collection {
    print(item)
}
rust
for item in &collection {
    println!("{}", item);
}

for 循环 ​

for 循环适用于数组、列表、范围以及任何实现迭代器模式的类型:

auto
// 遍历数组
let arr = [1, 2, 3]
for n in arr {
    print(n)
}

// 遍历范围
for i in 0..10 {
    print(i)
}
rust
let arr = [1, 2, 3];
for n in arr {
    println!("{}", n);
}

for i in 0..10 {
    println!("{}", i);
}

在底层,for 循环使用迭代器。循环获取迭代器的所有权,重复调用 next() 直到返回 None。

迭代器方法 ​

注意:以下迭代器方法是 Rust Iterator 特征的标准方法。当使用 Auto 的 use.rust 时,可以直接在实现 Iterator 的类型上访问这些方法。

创建迭代器 ​

方法描述所有权
.iter()遍历引用借用每个元素
.iter_mut()遍历可变引用可变借用每个元素
.into_iter()遍历拥有的值获取所有权

消费适配器 ​

消费适配器调用 next() 并消耗迭代器:

rust
// sum — 消耗迭代器并返回总和
let v1 = vec![1, 2, 3];
let total: i32 = v1.iter().sum();  // 6

迭代器适配器 ​

迭代器适配器转换迭代器但不消耗它。它们是_惰性的_——在消费适配器被调用之前 不会执行任何操作:

rust
// map — 转换每个元素(惰性!)
let v1 = vec![1, 2, 3];
let v2: Vec<_> = v1.iter().map(|x| x + 1).collect();
// v2 为 [2, 3, 4]

// filter — 保留匹配谓词的元素(惰性!)
let shoes: Vec<Shoe> = shoes.into_iter()
    .filter(|s| s.size == shoe_size)
    .collect();

在迭代器方法中使用闭包 ​

迭代器方法通常接受闭包作为参数。以下示例使用 filter 和一个从环境中捕获 shoe_size 的闭包:

rust
fn shoes_in_size(shoes: Vec<Shoe>, shoe_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(|s| s.size == shoe_size).collect()
}

闭包 |s| s.size == shoe_size 从周围作用域捕获 shoe_size,并与每双鞋的 尺寸进行比较。

改进 I/O 项目 ​

借助闭包和迭代器,我们可以改进第 12 章的 minigrep 项目。

原始的 search 函数使用了一个可变的 results 向量:

auto
fn search(query String, contents String) []String {
    var results = List.new()
    for line in contents.lines() {
        if line.contains(query) {
            results.push(line)
        }
    }
    results
}

使用迭代器适配器,我们可以使代码更简洁,避免可变中间变量:

rust
pub fn search<'a>(query: &str, contents: &'a str) -> Vec<&'a str> {
    contents
        .lines()
        .filter(|line| line.contains(query))
        .collect()
}

这里使用 filter 只保留包含查询字符串的行,然后用 collect 将它们收集到 向量中。函数式风格最小化了可变状态,使代码更易理解。

闭包与迭代器速查 ​

特性AutoRust
闭包语法(x) =&gt; x + 1|x| x + 1
带类型的闭包(x int) -&gt; int { x + 1 }|x: i32| -&gt; i32 { x + 1 }
for 循环for x in list { ... }for x in &list { ... }
范围0..100..10
过滤.filter((s) =&gt; s.size == 10).filter(|s| s.size == 10)
映射.map((x) =&gt; x + 1).map(|x| x + 1)
收集.collect().collect()
求和.sum().sum()

总结 ​

本章介绍了两个关键的函数式编程特性:

  1. 闭包 — 捕获其环境的匿名函数

    • (params) =&gt; expr 语法
    • 从使用中推断类型
    • 通过不可变引用、可变引用或所有权捕获
    • Fn、FnMut、FnOnce 特征
  2. 迭代器 — 逐个处理元素的惰性序列

    • for 循环作为主要迭代机制
    • 消费适配器(.sum())和迭代器适配器(.map()、.filter())
    • 链式适配器实现复杂转换
  3. I/O 项目改进 — 用迭代器适配器简化 search

闭包和迭代器在 Rust 中是零成本抽象——它们编译为与手写循环相同的代码。 Auto 继承了这一性能模型。

在下一章中,我们将探索 Auto 的智能指针类型以及它们如何管理内存。

基于 MIT 许可发布。