当前位置:首页 > 文章列表 > 文章 > 软件教程 > Rust 闭包

Rust 闭包

2026-08-20 22:28:31 0浏览 收藏

Rust 中的闭包是一种匿名函数,它可以捕获并存储其所在环境中的变量。

Rust 闭包

闭包允许在定义作用域之外访问变量,并且可以在需要时将其移动或借用给闭包使用。

闭包在 Rust 中被广泛应用于函数式编程、并发编程和事件驱动编程等领域。

闭包与函数的区别

闭包和普通函数都能封装一段逻辑,但闭包多了一项核心能力——捕获外部环境中的变量。下表列出两者的主要差异:

特性闭包函数
匿名性没有名称,通常赋值给变量有固定的 fn 名称
环境捕获可以捕获外部变量不能捕获外部变量
定义方式|参数| 表达式fn 名称(参数)
类型推导参数和返回值类型通常可以省略必须显式指定参数和返回值类型
存储与传递可以作为变量、参数、返回值同样支持

闭包的声明与调用

闭包的基本语法如下:

let closure_name = |参数列表| 表达式或语句块;

参数可以有类型注解,也可以省略——Rust 编译器会根据上下文推断它们。

实例

fn main() {
    // 闭包:省略类型注解,编译器自动推断
    let add_one = |x| x + 1;
    println!("add_one(4) = {}", add_one(4)); // 输出: 5
    // 闭包:显式标注参数和返回值类型
    let multiply = |a: i32, b: i32| -> i32 { a * b };
    println!("multiply(3, 7) = {}", multiply(3, 7)); // 输出: 21
    // 多行闭包需要加大括号
    let greet = |name: &str| {
        let greeting = format!("Hello, {}!", name);
        greeting
    };
    println!("{}", greet("runoob")); // 输出: Hello, runoob!
}

闭包的调用方式与普通函数完全一致,在变量名后加括号并传入参数即可。

捕获外部变量

闭包最核心的特性是能够捕获其所在作用域中的变量。这是闭包与普通函数的本质区别。

闭包可以通过三种方式捕获外部变量:

捕获方式对应 Rust 语义说明
按引用捕获类似 &T默认行为,闭包借用变量,外部仍可使用
可变借用捕获类似 &mut T闭包需要修改变量,闭包本身须声明为 mut
按值捕获类似 T使用 move 关键字,将变量所有权移入闭包

对于那些实现了 Copy trait 的类型,比如 i32boolmove 并不会把变量“搬走”,而是直接拷贝出一份值,原来的外部变量依然可以继续使用。也正因为这样,想把所有权转移这件事演示清楚,通常要选 StringVec 这类非 Copy 类型,效果才够直观。

按引用捕获

默认情况下,闭包以不可变引用的方式借用外部变量。借用结束后,外部作用域可以继续使用该变量。

实例

fn main() {
    let text = String::from("runoob");
    // 闭包按引用捕获 text,不获取所有权
    let print_text = || println!("text = {}", text);
    print_text(); // 输出: text = runoob
    // 借用已结束,外部仍然可以使用 text
    println!("外部仍可使用: {}", text);
}

可变借用捕获

如果闭包需要修改捕获的变量,Rust 会以可变引用的方式捕获。此时闭包本身必须声明为 mut

实例

fn main() {
    let mut counter = 0;
    // 闭包以 &mut 方式捕获 counter,闭包本身也要声明为 mut
    let mut inc = || {
        counter += 1;
    };
    inc();
    inc();
    println!("counter = {}", counter); // 输出: counter = 2
}

按值捕获(move)

通过在闭包前添加 move 关键字,闭包会获取所捕获变量的所有权。所有权转移后,外部作用域将无法再使用该变量。

实例

fn main() {
    let owned = String::from("RUNOOB");
    // move 将 owned 的所有权转移进闭包
    let take_owned = move || println!("owned = {}", owned);
    take_owned(); // 输出: owned = RUNOOB
    // 若取消下面这行注释,将编译报错:owned 的所有权已被移入闭包
    // println!("{}", owned);
}

当需要将闭包传递到另一个线程或返回到作用域之外时,move 尤为常用——它确保闭包持有所需数据的所有权,不会出现悬垂引用。

闭包 Trait:Fn / FnMut / FnOnce

Rust 编译器会根据闭包捕获变量的方式,自动为闭包实现对应的 trait。理解这三个 trait 是使用闭包作为函数参数或返回值的关键。

Trait捕获方式可调用次数典型场景
Fn不可变借用(&T多次只读访问捕获变量
FnMut可变借用(&mut T多次需要修改捕获变量
FnOnce获取所有权(T仅一次消耗捕获变量,之后不可再调用

三者的继承关系为:FnFnMut 的子 trait,FnMutFnOnce 的子 trait。也就是说,一个实现了 Fn 的闭包,自动也实现了 FnMutFnOnce

注意:move 关键字只是强制获取所有权,并不意味着闭包一定是 FnOnce。如果闭包体中并没有消耗捕获的值,那么即使加了 move,闭包仍可能实现 Fn(可多次调用)。

实例

fn main() {
    let name = String::from("runoob");
    // Fn 闭包:只读取,不修改,可多次调用
    let greet = || println!("Hello, {}!", name);
    greet(); // 第一次调用
    greet(); // 第二调用,依然正常
    // FnMut 闭包:需要修改捕获变量
    let mut count = 0;
    let mut increment = || {
        count += 1;
        count
    };
    println!("increment() = {}", increment()); // 输出: 1
    println!("increment() = {}", increment()); // 输出: 2
    // FnOnce 闭包:消耗捕获的变量,只能调用一次
    let data = String::from("RUNOOB");
    let consume = move || {
        let _ = data; // 将 data 移出闭包环境,消耗了它
        println!("data has been consumed");
    };
    consume();
    // consume(); // 若取消注释,编译报错:FnOnce 闭包只能调用一次
}

闭包作为参数和返回值

闭包可以作为函数参数传入,也可以作为函数的返回值返回。这是闭包在实际开发中最常见的两种用法。

闭包作为参数

将闭包作为参数时,需要用泛型约束指定闭包的 trait 类型。以下示例中,参数 F 被约束为 Fn(i32) -> i32,表示接收一个 i32 参数并返回 i32 的闭包。

实例

// 定义一个函数,接受闭包作为参数
fn apply(val: i32, f: F) -> i32
where
    F: Fn(i32) -> i32, // F 必须实现 Fn(i32) -> i32
{
    f(val)
}
fn main() {
    let double = |x| x * 2;
    let result = apply(5, double);
    println!("Result: {}", result); // 输出: Result: 10
    // 也可以直接传入匿名闭包
    let result2 = apply(3, |x| x + 100);
    println!("Result2: {}", result2); // 输出: Result2: 103
}

闭包作为返回值

由于闭包的类型是匿名的,返回闭包时需要使用 impl TraitBox 来描述返回类型。

使用 impl Fn 返回闭包

当返回的闭包类型在编译期可以确定时,使用 impl Fn 即可,无需堆分配。

实例

// 返回一个闭包,捕获参数 x 并与传入值相加
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
    // 必须 move,否则 x 是局部引用,闭包返回后 x 已失效
    move |y| x + y
}
fn main() {
    let add_five = make_adder(5);
    println!("5 + 3 = {}", add_five(3)); // 输出: 5 + 3 = 8
    let add_ten = make_adder(10);
    println!("10 + 2 = {}", add_ten(2)); // 输出: 10 + 2 = 12
}

使用 Box 返回闭包

当需要在运行时动态选择不同闭包返回时,使用 Box 将闭包分配到堆上。

实例

fn make_adder(x: i32) -> Box i32> {
    Box::new(move |y| x + y)
}
fn main() {
    let add_ten = make_adder(10);
    println!("10 + 2 = {}", add_ten(2)); // 输出: 10 + 2 = 12
}
方式分配位置适用场景
impl Fn编译期可确定闭包类型,性能更优
Box运行时动态选择闭包,或需要跨函数存储闭包

常见应用场景

闭包在 Rust 的日常开发中随处可见,以下是几个最典型的应用场景。

迭代器中的闭包

闭包经常与迭代器方法配合使用,用于对集合元素进行批量处理。

实例

fn main() {
    let nums = vec![1, 2, 3, 4, 5];
    // map:对每个元素进行转换
    let squared: Vec = nums.iter().map(|x| x * x).collect();
    println!("平方: {:?}", squared); // 输出: [1, 4, 9, 16, 25]
    // filter:筛选满足条件的元素
    let even: Vec = nums.iter().filter(|x| *x % 2 == 0).collect();
    println!("偶数: {:?}", even); // 输出: [2, 4]
    // fold:将集合归约为单个值
    let sum: i32 = nums.iter().fold(0, |acc, x| acc + x);
    println!("求和: {}", sum); // 输出: 15
}

闭包与多线程

在多线程编程中,闭包常用于定义线程的执行体。move 关键字在此场景下几乎是必选的,因为它将数据的所有权移入新线程,避免跨线程引用失效。

实例

use std::thread;
fn main() {
    let nums = vec![1, 2, 3, 4, 5];
    // 为每个数字创建一个线程,move 将 num 的所有权移入线程
    let handles: Vec<_> = nums.into_iter().map(|num| {
        thread::spawn(move || {
            num * 2
        })
    }).collect();
    // 等待所有线程完成并收集结果
    for handle in handles {
        let result = handle.join().unwrap();
        println!("Result: {}", result);
    }
}

闭包与错误处理

闭包可以返回 ResultOption 类型,配合迭代器方法实现简洁的错误处理逻辑。

实例

fn main() {
    let nums = vec![3, -1, 4, -5, 9];
    // 使用闭包查找第一个正数
    let first_positive = nums.iter().find(|&&x| x > 0);
    match first_positive {
        Some(&n) => println!("第一个正数: {}", n), // 输出: 第一个正数: 3
        None => println!("没有正数"),
    }
    // 使用闭包过滤并转换,配合 Result 处理可能的错误
    let results: Vec> = nums.iter().map(|&n| {
        if n > 0 {
            Ok(n * 10)
        } else {
            Err("负数无法处理")
        }
    }).collect();
    for r in results {
        match r {
            Ok(v) => println!("成功: {}", v),
            Err(e) => println!("错误: {}", e),
        }
    }
}

性能与生命周期

闭包的性能

Rust 的闭包是轻量级的。编译器会对闭包进行内联优化,使得闭包调用的开销接近于直接调用普通函数。闭包本身不引入额外的虚函数调用或堆分配(除非显式使用 Box)。

闭包与生命周期

闭包的生命周期,和它所捕获的变量是绑在一起的。Rust 的生命周期系统会把这件事盯得很紧:闭包绝不会比它捕获的任何变量“活”得更久——一旦闭包引用了某个局部变量,编译器就在编译期直接拦住你,避免把这个闭包返回到该变量作用域之外。

实例

fn main() {
    let text = String::from("runoob");
    // 正确:闭包在 text 的作用域内使用
    let print_text = || println!("{}", text);
    print_text(); // 输出: runoob
    // 如果尝试返回这个闭包,编译器会报错:
    // fn make_closure() -> impl Fn() {
    //     let text = String::from("runoob");
    //     || println!("{}", text)  // 错误:text 的生命周期不够长
    // }
    // 解决方法:使用 move 将所有权移入闭包
}

完整示例

以下示例综合演示了闭包的声明、捕获外部变量、作为参数传递等核心用法。

实例

// 定义一个函数,接受闭包作为参数
fn apply_operation(num: i32, operation: F) -> i32
where
    F: Fn(i32) -> i32,
{
    operation(num)
}
fn main() {
    let num = 5;
    // 定义闭包:对数字进行平方运算
    let square = |x| x * x;
    // 将闭包作为参数传入函数
    let result = apply_operation(num, square);
    println!("Square of {} is {}", num, result); // 输出: Square of 5 is 25
    // 也可以直接传入匿名闭包
    let result2 = apply_operation(num, |x| x * x * x);
    println!("Cube of {} is {}", num, result2); // 输出: Cube of 5 is 125
}

运行该程序,输出如下:

Square of 5 is 25
Cube of 5 is 125

总结

Rust 的闭包是一种强大的抽象,它提供了一种灵活且表达力强的方式来封装逻辑。

闭包可以捕获环境变量,并且可以作为参数传递或作为返回值返回。闭包与迭代器结合使用,可以方便地实现复杂的数据处理任务。

Rust 的闭包设计兼顾了安全性、性能和生命周期——编译器会在编译期确保闭包不会引用已失效的变量,并通过内联优化保证闭包调用的零开销抽象。

PHP password_needs_rehash 怎么做密码升级:算法变更、登录时机与回滚边界
下一篇
PHP password_needs_rehash 怎么做密码升级:算法变更、登录时机与回滚边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • ljg-skills -
    ljg-skills
    ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
    5037次使用
  • MELO音乐 - AI 音乐生成平台,支持多模态创作能力
    MELO音乐
    MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
    4573次使用
  • UniScribe - AI 免费在线音视频转文字平台
    UniScribe
    UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
    4521次使用
  • 剧云 - 免费 AI 智能中文剧本创作平台
    剧云
    剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
    4779次使用
  • 万象有声 - AI 一站式有声内容创作平台
    万象有声
    万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
    4731次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码