Rust中的闭包

Rust 的闭包在功能和用途上与其他语言的lambda相同,都是在本地定义的小型匿名函数,可以捕获外部变量。

基本语法

Rust 闭包的基本定义形式如下

  • |参数列表|:用竖线包裹参数,多个参数用逗号分隔(如|x, y|)。
  • 函数体:可以是单个表达式(省略{}),也可以是多个语句(需用{}包裹)。
let closure_name = |参数列表| -> 返回类型 {
    表达式或语句块
};

类型与特征

闭包在 Rust 中有独立的匿名类型,并自动实现以下特征之一:

Trait含义适用场景
Fn不可变捕获环境(只读访问)可多次调用,捕获的变量仍可在外部使用
FnMut可变捕获环境(可修改捕获的变量)可多次调用,但捕获的变量在外部需为mut
FnOnce转移捕获变量的所有权(move
关键字)
只能调用一次(所有权转移后无法再次使用)

示例

pub fn closure_test() {
    let greeting = String::from("Hi");

    // Fn(只读)
    let say_hi = || println!("{}", greeting);
    say_hi();
    println!("greeting: {}", greeting);

    // FnMut(可变)
    let mut num = 0;
    let mut add_one = || num += 1;
    add_one();
    add_one();
    println!("num: {}", num);

    // FnOnce(移动)
    let s = String::from("Rust");
    let consume = move || {
        let tmp = s; // 闭包内字符串被移动到tmp,失去所有权
        println!("{}", tmp);
    };
    consume();
    // consume();  // 编译错误:闭包内捕获的s已被移动
    // println!("s: {}", s); // 编译错误:s已被移动
}

move语义

默认情况下,闭包会根据对变量的操作自动选择捕获方式(不可变借用、可变借用),尽量避免所有权转移。而move关键字会强制闭包 “夺取” 捕获变量的所有权(对于非Copy类型),或复制变量(对于Copy类型)。

  • Copy类型(如String,Vec)的所有权具有唯一性,move会让闭包直接获取其所有权,原变量在闭包外不可再使用(所有权已转移)。
  • Copy类型在赋值时会自动复制,move会让闭包获取变量的副本,原变量仍可正常使用。

move与闭包Trait

move仅决定闭包如何捕获变量(所有权转移),但闭包最终实现Fn/FnMut/FnOnce中的哪个 trait,取决于闭包如何使用捕获的变量(而非move本身)。

move + 只读使用 → 实现Fn

move闭包仅只读访问捕获的变量(不修改、不消耗),则实现Fn trait,可多次调用。

let s = String::from("hello");
// move捕获s的所有权,闭包仅读取s
let closure = move || println!("{}", s);

closure(); // 正常
closure(); // 正常(可多次调用,因未消耗s)
// println!("{}", s); // 编译错误:s的所有权已被转移到闭包中

move + 修改使用 → 实现FnMut

move闭包修改捕获的变量(但不消耗),则实现FnMut trait,闭包自身需为mut

////////////////////////////////////
// 非copy语义类型,所有权被转移
let mut v = vec![1, 2];
// move捕获v的所有权,闭包修改v
let mut closure = move || v.push(3);

closure(); // v变为 [1,2,3]
closure(); // v变为 [1,2,3,3](可多次调用)
// println!("{}", v); // 编译错误:s的所有权已被转移到闭包中

////////////////////////////////////
// copy语义类型,复制一个副本,修改不影响外部
let mut num = 0;
let mut add_one = move || {
    num += 1;
    println!("num: {}", num);
};
add_one(); // 1
add_one(); // 2
println!("num: {}", num); // 0

move + 消耗使用 → 实现FnOnce

move闭包消耗捕获的变量(如调用drop或转移所有权给其他对象),则实现FnOnce trait,只能调用一次。

fn test_fnonce() {
    let s = String::from("hello");
    // move捕获s的所有权,闭包消耗s(转移给println!)
    let closure = move || {
        println!("{}", s); 
        s // 这里s被消耗
    };

    closure(); // 正常
    // closure(); // 编译错误:s已被消耗,闭包只能调用一次
}

典型使用场景

move主要用于解决生命周期问题:

  • 线程间传递闭包:线程的生命周期是独立的,无法保证外部变量的生命周期能覆盖线程的。此时move可将变量所有权转移到线程内部,避免悬垂引用
  • 延长变量生命周期:当闭包需要 “带走” 变量并在更晚的时候使用时,move可确保变量的所有权随闭包一起存在,避免提前销毁。

实现原理

闭包在编译时会被转换为一个匿名结构体类型(即使两个闭包代码完全相同,也会生成不同的类型)。结构体的字段为捕获的变量,实际字段类型取决于捕获方式(借用、所有权)。

  • 若不捕获任何变量:结构体为零大小(ZST,无字段),不占用内存空间,调用成本极低(编译器可完全优化掉)。
  • 若捕获多个变量:结构体字段按声明顺序连续存储(类似普通结构体)。
    • 捕获引用时:结构体中仅存储变量的引用;
    • 捕获所有权时:结构体存储被捕获变量的实际值(拥有所有权)。

闭包与对应的结构体:

let x = 10;
let y = String::from("hello");
let closure = |z| x + z; // 捕获x(不可变借用),不捕获y


///////////////////////////////////////////////////
// 以上闭包会生成类似下面的
// 匿名结构体,命名为ClosureType仅作示例
struct ClosureType<'a> {
    x: &'a i32, // 捕获x的不可变引用(因仅读取x)
}

impl<'a> Fn<(i32,)> for ClosureType<'a> {
    type Output = i32;
    // call方法的逻辑即闭包体:x + z
    fn call(&self, args: (i32,)) -> i32 {
        let (z,) = args; // 解构参数
        *self.x + z // 使用结构体字段x(不可变引用)
    }
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值