Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

函数与闭包

一、 函数 (Functions)

函数是 Rust 代码的静态骨架。它们必须在编译时拥有明确的签名。

1. 基础语法与显式返回

Rust 的函数参数必须标注类型,返回类型使用 -> 标注。

// 逻辑概括:参数必须显式注明类型,最后一行表达式作为返回值
fn calculate_score(points: i32, multiplier: i32) -> i32 {
    if points < 0 {
        return 0; // 使用 return 提前退出
    }
    points * multiplier // 隐式返回(无分号)
}
fn main() {
    let score = calculate_score(10, 3);
    println!("最终得分: {score}");
}

2. 函数指针 (fn 类型)

函数本身可以作为参数传递,也可以存储在变量中。它的类型是小写的 fn

fn add_one(x: i32) -> i32 { x + 1 }

fn do_math(f: fn(i32) -> i32, value: i32) -> i32 {
    f(value)
}

fn main() {
    let result = do_math(add_one, 5);
    println!("函数指针调用结果: {result}"); // 6
}

二、 闭包 (Closures)

Rust 中的闭包(closures)是一种匿名函数,可以捕获其环境中的变量。闭包类似于其他语言中的 lambda 表达式,但 Rust 的闭包系统与所有权和借用紧密集成,确保内存安全。闭包可以作为函数参数、返回值,或存储在变量中,常用于迭代器、线程和回调。Rust 闭包实现了Fn trait 家族(Fn、FnMut、FnOnce),根据捕获方式决定其行为。最核心的特性是 捕获环境 。它们通常比函数更简洁,且支持类型推导。

1. 语法与自动推导

闭包不强制写类型,编译器会根据第一次调用的上下文锁定类型。语法:|params| expression{ body }

fn main() {
    // 闭包标准语法
    let closure_annotated = |x: i32| -> i32 { x + 1 };
  
    // 自动推导简写
    let closure_inferred = |x| x + 1;

    println!("{}", closure_annotated(1));
    println!("{}", closure_inferred(1));
}

2. 捕获方式:不可变、可变、移动

闭包通过三种方式从作用域捕获变量:

  • 不可变借用 (&T) :默认方式。
  • 可变借用 (&mut T) :当闭包内部修改变量时。
  • 移动所有权 (T) :使用 move 关键字,常用于异步或多线程。
fn main() {
    let x = 4;
    let equal_to_x = |z| z == x;  // 借用 x (&x)
    println!("相等?{}", equal_to_x(4));  // 输出: 相等?true
    println!("x 仍有效: {}", x);  // x 未移动
    //可变借用
    let mut count = 0;
    let mut inc = || {
        count += 1; // 自动推导为 可变借用
        println!("当前计数: {count}");
    };
    inc();
    inc();
    // 强制移动所有权
    let text = String::from("hello");
    let print_text = move || println!("移动后的文本: {text}");
    print_text();
    // println!("{text}"); // ❌ 报错:text 已移动到闭包中
}

三、 闭包特征 (Fn, FnMut, FnOnce)

当闭包作为参数传递时,我们需要使用这三个 Trait 来约束它:

  • FnOnce :调用一次,消耗闭包(可能移动捕获)。
  • FnMut :可多次调用,可修改捕获。
  • Fn :可多次调用,只读捕获。
fn run_once<F>(f: F) where F: FnOnce() {
    f();
}

fn main() {
    let s = String::from("once");
    let consume_s = || drop(s); // 该闭包消费了 s 的所有权
  
    run_once(consume_s);
    // run_once(consume_s); // ❌ 报错:闭包已被消费
}

四、 高级进阶:函数与闭包作为返回值

这是 Rust 中最具灵活性的部分。由于闭包没有具体的名字,返回它们需要特殊的处理。

1. 返回普通函数指针 (fn)

适用于逻辑固定、不捕获外部变量的情况。

fn apply<F>(f: F, x: i32) -> i32
where
    F: FnOnce(i32) -> i32,  // bound FnOnce
{
    f(x)
}

fn main() {
    let double = |n| n * 2;
    println!("结果: {}", apply(double, 5));  // 输出: 结果: 10
}

2. 返回闭包:静态分发 (impl Trait)

这是返回闭包最常用的方式。它效率高(无堆分配),但要求所有分支返回同一种闭包。

fn create_multiplier(factor: i32) -> impl Fn(i32) -> i32 {
    // 关键:必须使用 move,将 factor 移入闭包
    // 否则 factor 会在函数结束时释放,导致引用失效
    move |x| x * factor
}

fn main() {
    let double = create_multiplier(2);
    println!("3 的两倍是: {}", double(3));
}

3. 返回闭包:动态分发 (Box<dyn Trait>)

如果你需要根据逻辑返回不同的闭包(比如在 if/else 分支中返回不同的闭包代码块),必须使用 Box

fn get_closure(mode: bool) -> Box<dyn Fn(i32) -> i32> {
    if mode {
        Box::new(|x| x + 1)
    } else {
        Box::new(|x| x * 2)
    }
}

fn main() {
    let f = get_closure(false);
    println!("执行结果: {}", f(5)); // 10
}

总结对比

特性普通函数 (fn)impl Trait 闭包Box<dyn Trait> 闭包
捕获变量不支持支持(需用 move支持(需用 move
返回类型fn(A) -> Bimpl Fn(A) -> BBox<dyn Fn(A) -> B>
内存位置代码段
性能极高(静态)高(静态)略低(动态寻址)
适用场景简单、纯粹的逻辑性能敏感、单一返回路径需要根据条件返回不同闭包

💡 核心避坑指南

在返回闭包时,忘记写 move 是新手最常见的错误。

记住 :闭包默认会尝试通过“引用”来捕获环境中的变量。但当函数结束时,这些变量会被销毁,所以闭包必须通过 move 把它们“打包带走”,否则你会得到一个“悬垂引用”的报错。