rust语言闭包特征Fn、FnMut和FnOnce

在 Rust 语言中,FnFnMut 和 FnOnce 是闭包(Closures)特征(Traits)。它们定义了闭包可以捕获其环境(即捕获外部变量)的不同方式。
理解这三个 Trait 是理解 Rust 中函数式编程和所有权管理的关键。它们组成了 Rust 闭包类型的层次结构。

核心概念:捕获(Capturing)外部变量

闭包可以捕获其定义范围内的变量。这三个 Trait 定义了这些捕获是如何发生的:
 
Trait描述捕获方式能否多次调用?
FnOnce 消耗性闭包 获取捕获变量的所有权 只能调用一次
FnMut 可变借用闭包 可变引用借用捕获变量 可以多次调用(需要可变访问)
Fn 不可变借用闭包 不可变引用借用捕获变量 可以多次调用(安全并行)
 
层次关系
Fn 继承自 FnMut,而 FnMut 继承自 FnOnce
如果一个闭包实现了 Fn,它一定也实现了 FnMut 和 FnOnce。反之则不然。

1. FnOnce (Call Once)

只能调用一次的闭包。它通过获取捕获变量的所有权来执行其操作。在闭包执行完毕后,被捕获的变量就被消耗了,因此不能再次调用。
使用场景: 需要消耗捕获变量的函数,如线程创建(将数据所有权移入新线程)或 std::mem::drop
let data = String::from("所有权");

// FnOnce: 捕获了 data 的所有权
let consume_closure = || {
    println!("消耗数据: {}", data);
    std::mem::drop(data); // 消耗 data
};

// 只能调用一次:
consume_closure(); 

// consume_closure(); // 错误!data 已经被消耗了

2. FnMut (Call Mutably)

可以多次调用的闭包,但需要可变地借用捕获的变量。它需要对环境进行修改(例如修改计数器)。
使用场景: 需要在每次调用时改变状态的迭代器(如 filter_map)或需要修改外部变量的场景。
let mut counter = 0;

// FnMut: 可变地借用 counter
let mut count_closure = || {
    counter += 1; 
    println!("计数: {}", counter);
};

count_closure(); // 第一次调用:借用开始并立即结束
count_closure(); // 第二次调用:借用开始并立即结束

// ❌ 错误!这里无法编译通过:可参考下面的NLL机制解释。
println!("counter is: {}", counter); 

// --- 演示真正的借用冲突 ---
let closure_ref = &mut count_closure; // 将闭包本身借用出来

// ❌ 错误!这里无法编译通过:
// println!("counter is: {}", counter); 
// 
// 错误信息大致为:`error[E0502]: cannot borrow counter as immutable because it is also borrowed as mutable`
// 因为 closure_ref 正在持有对 counter 的可变借用。

closure_ref(); // 使用借用后的闭包
在 Rust 的 NLL (Non-Lexical Lifetimes) 机制下:

如果闭包只是被定义并简单调用: 编译器发现闭包调用完后没人在后面用它了,它会很聪明地在调用完后立即释放借用。

但如果闭包被再次借用(&mut count_closure): 这种情况极大地延伸了闭包的生命周期。编译器认为:“既然你在后面还要通过一个引用来操作这个闭包,那么这个闭包内部捕获的所有变量(counter)也必须从定义那一刻起一直被锁定到最后一次使用结束。”

简单来说:
当你写下 let closure_ref = &mut count_closure; 并在后面使用它时,你实际上是在告诉编译器:“请确保从闭包定义开始,一直到我最后一次用 closure_ref 结束,这期间谁都别想动 counter。”

3. Fn (Call Immutable)

可以多次调用的闭包,并且只不可变地借用捕获的变量。这是最灵活、最安全的闭包类型,可以在多线程环境中安全共享。
使用场景: 大多数迭代器操作(mapfilter 等),它们只读取外部数据而不修改或消耗它。
let factor = 2;

// Fn: 不可变地借用 factor
let multiply_closure = |x| x * factor;

// 可以多次调用:
println!("结果: {}", multiply_closure(5)); // 结果: 10
println!("结果: {}", multiply_closure(10)); // 结果: 20

// 可以在调用闭包的同时使用 factor
println!("原始因子: {}", factor);

4. 如何在函数签名中使用

当您编写一个接受闭包作为参数的泛型函数时,您需要指定您期望闭包实现哪个 Trait:
// 接受一个只能调用一次的闭包
fn call_once<F: FnOnce()>(f: F) {
    f();
}

// 接受一个需要可变状态的闭包
fn call_mutably<F: FnMut()>(mut f: F) {
    f();
    f();
}

// 接受一个只读闭包(最灵活)
fn call_immutable<F: Fn()>(f: F) {
    f();
    f();
    f();
}

参考资料:

1.rust语言基础

 

posted @ 2025-12-15 17:12  PKICA  阅读(6)  评论(0)    收藏  举报