Fn、FnMut、FnOnce
闭包特征
Fn、FnMut、FnOnce 是 CoddyKit 上的免费 Learn Rust Coding 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Learn Rust Coding 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Learn Rust Coding 课程共包含 4 节课。
闭包特征
每个闭包都会根据使用捕获值的方式,自动实现以下三个特征中的一个或多个:
Fn— 以不可变方式借用。FnMut— 以可变方式借用。FnOnce— 获取所有权,只能调用一次。
fn main() {
let x = 5;
let show = || println!("x is {}", x); // implements Fn
show();
show();
}Fn:不可变借用
只读取捕获值的闭包会实现 Fn。它可以被调用多次,而不会改变任何内容。
fn main() {
let greeting = String::from("Hi");
let say = || println!("{}", greeting);
say();
say();
}FnMut:可变借用
会更改捕获值的闭包会实现 FnMut。要调用它,闭包变量本身必须是 mut。
fn main() {
let mut count = 0;
let mut increment = || {
count += 1;
println!("count {}", count);
};
increment();
increment();
}FnOnce:获取所有权
将捕获值移出环境的闭包会实现 FnOnce。它只能被调用一次。
fn main() {
let name = String::from("Ada");
let consume = move || {
let owned = name;
println!("consumed {}", owned);
};
consume();
}特征层次结构
这些特征构成一个层次结构:
- 每个
Fn同时也是FnMut和FnOnce。 - 每个
FnMut同时也是FnOnce。
因此,Fn 最灵活,而 FnOnce 的限制最多。
fn main() {
let value = 42;
let read = || value; // Fn, so also FnMut and FnOnce
println!("{}", read());
}将 Fn 传递给函数
函数可以通过指定所需的特征来接受闭包。请使用带有特征约束的泛型。
fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
f(x)
}
fn main() {
let double = |n| n * 2;
println!("{}", apply(double, 9));
}接受 FnMut
如果函数需要修改捕获的状态,请要求使用 FnMut,并将参数标记为 mut。
fn call_twice<F: FnMut()>(mut f: F) {
f();
f();
}
fn main() {
let mut total = 0;
call_twice(|| total += 1);
// note: total is borrowed during the call
}接受 FnOnce
如果函数只调用闭包一次,并且可能让闭包消耗某些值,请要求使用 FnOnce。
fn run_once<F: FnOnce() -> String>(f: F) {
let result = f();
println!("{}", result);
}
fn main() {
let s = String::from("done");
run_once(move || s);
}move 关键字
move 关键字会强制闭包获取捕获变量的所有权,而不是借用它们。
当闭包必须比创建它的作用域存活更久时,这一点至关重要,例如在线程中使用闭包时。
fn main() {
let data = vec![1, 2, 3];
let printer = move || println!("{:?}", data);
printer();
}Rust 如何选择特征
您不需要手动选择特征。编译器会根据闭包主体决定:
- 只读取 →
Fn - 进行修改 →
FnMut - 将捕获的值移出 →
FnOnce
fn main() {
let mut log = Vec::new();
let mut record = |x: i32| log.push(x); // FnMut
record(1);
record(2);
println!("{:?}", log);
}选择合适的约束
一般来说:
- 只需要调用和读取时,接受
Fn。 - 闭包会修改状态时,接受
FnMut。 - 调用一次就足够时,接受
FnOnce。
请选择符合需求的最宽松约束。
fn each<F: Fn(i32)>(items: &[i32], f: F) {
for &i in items {
f(i);
}
}
fn main() {
each(&[1, 2, 3], |n| println!("got {}", n));
}快速检查
将闭包的行为与其特征匹配起来。
总结
三个闭包特征:
Fn— 以不可变方式借用,可以重复调用。FnMut— 以可变方式借用,可以更改状态。FnOnce— 获取所有权,只能调用一次。
编译器会推断特征;move 会强制闭包捕获所有权。
fn main() {
let base = 5;
let adder = move |x: i32| x + base; // Fn, captures base by move
println!("{}", adder(10));
}常见问题解答
「Fn、FnMut、FnOnce」课时是免费的吗?
是的 — 「Fn、FnMut、FnOnce」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Learn Rust Coding 课程的其余内容,请升级到 CoddyKit PRO。 Learn Rust Coding 课程共包含 4 节课。
「Fn、FnMut、FnOnce」这节课中我会学到什么?
闭包特征 你通过在浏览器中直接运行的动手代码来练习 Learn Rust Coding,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Learn Rust Coding 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Learn Rust Coding 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「Fn、FnMut、FnOnce」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Learn Rust Coding 课中编写并运行代码吗?
能。每节 Learn Rust Coding 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。