0Pricing
Learn Rust Coding · 课时

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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 定义函数
  2. 表达式与语句
  3. 闭包
  4. Fn、FnMut、FnOnce
← 返回 Learn Rust Coding