Rust 如何处理闭包:Fn、FnMut 和 FnOnce

在 Rust 编程语言中,闭包是一种强大的特性,允许你定义匿名函数并捕获周围环境中的变量。Rust 的闭包系统由三个核心特征定义------Fn、FnMut 和 FnOnce。理解这些特征对于掌握 Rust 的闭包机制以及编写高效、安全的代码至关重要。

本文将详细介绍这三个特征,包括它们的定义、用例、适用场景和最佳实践,并提供代码示例,帮助你全面学习相关概念。

什么是 Fn、FnMut 和 FnOnce?

Fn、FnMut 和 FnOnce 是 Rust 标准库中定义的三个特征,用于描述闭包(或任何可调用对象)的行为。它们的主要区别在于如何访问捕获的变量以及在被调用时的所有权规则:

  • FnOnce:表示闭包可以被调用一次。被调用后,闭包本身会被消耗,不能再被使用。
  • FnMut:表示闭包可以被多次调用,并且在被调用时可以修改捕获的变量。
  • Fn:表示闭包可以被多次调用,并且只读取捕获的变量而不修改它们。

这三个特征之间存在继承关系:

  • Fn 继承自 FnMut,而 FnMut 继承自 FnOnce。
  • 因此,如果一个闭包实现了 Fn,它也会自动实现 FnMut 和 FnOnce;如果一个闭包实现了 FnMut,它也会实现 FnOnce。

每个特征的定义

FnOnce

FnOnce 特征定义了一个 call_once 方法,其签名如下:

rust 复制代码
pub trait FnOnce<Args> {
    type Output;
    fn call_once(self, args: Args) -> Self::Output;
}
  • 特性:call_once 接收 self(而不是 &self 或 &mut self),这意味着当闭包被调用时,它会转移其所有权,因此只能被调用一次。
  • 用例:适用于闭包需要移动捕获的变量或执行一次性操作的场景。

FnMut

FnMut 特征定义了一个 call_mut 方法,其签名如下:

rust 复制代码
pub trait FnMut<Args>: FnOnce<Args> {
    fn call_mut(&mut self, args: Args) -> Self::Output;
}
  • 特性:call_mut 接收 &mut self,允许闭包在被调用时修改其内部状态或捕获的变量,并且可以被多次调用。
  • 用例:适用于闭包需要在多次调用中修改其环境的场景。

Fn

Fn 特征定义了一个 call 方法,其签名如下:

rust 复制代码
pub trait Fn<Args>: FnMut<Args> {
    fn call(&self, args: Args) -> Self::Output;
}
  • 特性:call 接收 &self,这意味着闭包借用不可变。它可以被多次调用而不修改环境。
  • 用例:适用于闭包需要被多次调用且只读取数据的场景。

闭包如何实现这些特征

Rust 编译器会根据闭包如何使用捕获的变量自动确定它实现了哪些特征。闭包可以以三种方式捕获变量:

  • 按值(move) :闭包获取变量的所有权。
  • 按可变引用(&mut) :闭包捕获变量的可变引用。
  • 按不可变引用(&) :闭包捕获变量的不可变引用。

实现的特征取决于捕获变量的使用方式:

  • 仅实现 FnOnce:闭包移动了捕获的变量。
  • 实现 FnMut 和 FnOnce:闭包修改了捕获的变量。
  • 实现 Fn、FnMut 和 FnOnce:闭包只读取捕获的变量。

代码示例

实现 FnOnce 的闭包

rust 复制代码
fn main() {
    let s = String::from("hello");
    let closure = move || {
        drop(s); // 移动 s 并释放它
    };
    closure(); // 被调用一次
    // closure(); // 错误:闭包已被消耗
}

解释:闭包通过 move 捕获 s,获取其所有权并在被调用时释放它。由于 s 被移动,闭包只能被调用一次,因此它仅实现 FnOnce。

实现 FnMut 的闭包

rust 复制代码
fn main() {
    let mut s = String::from("hello");
    let mut closure = || {
        s.push_str(" world"); // 修改 s
    };
    closure(); // 第一次调用
    closure(); // 第二次调用
    println!("{}", s); // 输出 "hello world world"
}

解释:闭包通过可变引用捕获 s 并在每次调用时修改它。由于它需要修改环境,因此它实现了 FnMut 和 FnOnce。

实现 Fn 的闭包

rust 复制代码
fn main() {
    let s = String::from("hello");
    let closure = || {
        println!("{}", s); // 读取 s 而不修改
    };
    closure(); // 第一次调用
    closure(); // 第二次调用
}

解释:闭包通过不可变引用捕获 s 并只读取它而不修改。因此,它实现了 Fn、FnMut 和 FnOnce。

在函数参数中使用这些特征

闭包可以作为参数传递给函数,函数需要使用特征约束来指定所需的闭包行为。

使用 FnOnce

rust 复制代码
fn call_once<F>(f: F)
where
    F: FnOnce(),
{
    f();
}

fn main() {
    let s = String::from("hello");
    call_once(move || {
        drop(s);
    });
}

解释:call_once 接受一个 FnOnce 闭包并调用它一次,适用于移动捕获变量的闭包。

使用 FnMut

rust 复制代码
fn call_mut<F>(mut f: F)
where
    F: FnMut(),
{
    f();
    f();
}

fn main() {
    let mut s = String::from("hello");
    call_mut(|| {
        s.push_str(" world");
    });
    println!("{}", s); // 输出 "hello world world"
}

解释:call_mut 接受一个 FnMut 闭包并调用它两次。闭包可以修改捕获的变量。注意 f 必须被声明为 mut。

使用 Fn

rust 复制代码
fn call_fn<F>(f: F)
where
    F: Fn(),
{
    f();
    f();
}

fn main() {
    let s = String::from("hello");
    call_fn(|| {
        println!("{}", s);
    });
}

解释:call_fn 接受一个 Fn 闭包并调用它两次。闭包只读取捕获的变量。

何时使用每个特征?

选择正确的特征取决于闭包所需的行为:

最佳实践

  • 优先使用 Fn:如果闭包不需要修改变量,使用 Fn 以获得最大兼容性。
  • 在需要修改时使用 FnMut:当闭包需要更新状态时选择 FnMut。
  • 在单次使用时使用 FnOnce:如果闭包移动变量或执行一次性任务,使用 FnOnce。
  • 为 API 选择正确的特征:对于单次调用使用 FnOnce,对于多次只读调用使用 Fn,对于多次调用并修改使用 FnMut。
  • 注意生命周期:确保捕获的变量存活时间足够长,以避免借用错误。

原文:dev.to/leapcell/ho...

相关推荐
寻月隐君5 小时前
硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端
后端·rust·github
m0_4805026410 小时前
Rust 入门 泛型和特征-特征对象 (十四)
开发语言·后端·rust
RustFS11 小时前
如何用 Rust 对 RustFS MCP Server 进行扩展?
rust
我是前端小学生3 天前
一文梳理Rust语言中的可变结构体实例
rust
Source.Liu3 天前
【unitrix数间混合计算】2.21 二进制整数加法计算(bin_add.rs)
rust
Include everything3 天前
Rust学习笔记(二)|变量、函数与控制流
笔记·学习·rust
Source.Liu3 天前
【unitrix数间混合计算】2.20 比较计算(cmp.rs)
rust
许野平3 天前
Rust:构造函数 new() 如何进行错误处理?
开发语言·后端·rust
许野平3 天前
Rust:专业级错误处理工具 thiserror 详解
rust·error·错误处理·result·thiserror