当前位置: 首页 > news >正文

【Rust闭包】rust语言闭包函数原理用法汇总与应用实战

在这里插入图片描述

✨✨ 欢迎大家来到景天科技苑✨✨

🎈🎈 养成好习惯,先赞后看哦~🎈🎈

🏆 作者简介:景天科技苑
🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。
🏆《博客》:Rust开发,Python全栈,Golang开发,云原生开发,PyQt5和Tkinter桌面开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi,flask等框架,云原生K8S,linux,shell脚本等实操经验,网站搭建,数据库等分享。

所属的专栏:Rust语言通关之路
景天的主页:景天科技苑

在这里插入图片描述

文章目录

  • Rust闭包
    • 1. 闭包基础
      • 1.1 什么是闭包
      • 1.2 闭包的基本语法
      • 1.3 闭包与函数的比较
    • 2. 闭包的捕获方式
      • 2.1 Fn:不可变借用
      • 2.2 FnMut:可变借用
      • 2.3 FnOnce:获取所有权
      • 2.4 move关键字
    • 3. 闭包作为参数和返回值
      • 3.1 闭包作为函数参数
      • 3.2 闭包作为结构体字段
      • 3.3 闭包作为函数返回值
    • 4. 闭包的实际应用案例
      • 4.1 缓存/记忆化模式
    • 5. 高级闭包技巧
      • 5.1 闭包与生命周期
    • 6. 性能考虑
    • 7. 总结

Rust闭包

闭包(Closure)是 Rust 中一个强大且灵活的特性,它允许你捕获环境中的变量并在稍后执行。Rust 的闭包设计既高效又安全,是函数式编程风格的重要组成部分。
Rust 的 闭包(closures)是可以保存进变量或作为参数传递给其他函数的匿名函数。
可以在一个地方创建闭包,然后在不同的上下文中执行闭包运算。
不同于函数,闭包允许捕获调用者作用域中的值。

1. 闭包基础

1.1 什么是闭包

闭包是一种可以捕获其环境的匿名函数。与普通函数不同,闭包可以访问定义它的作用域中的变量。

fn main() {let x = 4;// 定义一个闭包,捕获变量xlet equal_to_x = |z| z == x;let y = 4;assert!(equal_to_x(y));
}

在这个例子中,闭包equal_to_x捕获了外部变量x,这是普通函数无法做到的。

1.2 闭包的基本语法

Rust闭包的基本语法如下:
两个竖线之间,是参数。竖线的后面是返回值,花括号里面是函数体,花括号可以省略

let closure_name = |parameters| -> return_type { body };

类型标注是可选的,Rust通常能推断出参数和返回值的类型:

// 完整形式
let add_one = |x: i32| -> i32 { x + 1 };// 简化形式(类型推断)。但是不能推导多次不同的类型,同一个闭包函数只能推导出一次类型
let add_one = |x| x + 1;

1.3 闭包与函数的比较

闭包和普通函数的几个关键区别:
闭包使用 || 而不是()来包围参数
闭包可以省略类型标注(编译器通常能推断)
闭包可以捕获其环境中的变量

// 函数
fn function(x: i32) -> i32 { x + 1 }
// 闭包
let closure = |x| x + 1;

2. 闭包的捕获方式

当闭包从环境中捕获一个值,闭包会在闭包体中储存这个值以供使用。
这会使用内存并产生额外的开销,当执行不会捕获环境的更通用的代码场景中我们不希望有这些开销。
因为函数从未允许捕获环境,定义和使用函数也就从不会有这些额外开销。
闭包可以通过三种方式捕获其环境,他们直接对应函数的三种获取参数的方式:获取所有权,不可变借用和可变借用。
这三种捕获值的方式被编码为如下三个 Fn trait:
Fn:从其环境不可变的借用值 。可以多次调用,不能修改捕获的变量
FnMut:可变的借用值所以可以改变其环境。可以多次调用,可以修改捕获的变量
FnOnce:消费从周围作用域捕获的变量,闭包周围的作用域被称为其 环境,environment。
为了消费捕获到的变量,闭包必须获取其所有权并在定义闭包时将其移动进闭包。
其名称的 Once 部分代表了闭包不能多次获取相同变量的所有权,所以它只能被调用一次。

当创建一个闭包时,rust会根据其如何使用环境中的变量来推断我们希望如何引用环境。
由于所有闭包都可以被调用至少一次,因此所有闭包都实现了FnOnce。
没有移动被捕获变量的所有权到闭包的闭包函数也实现了FnMut。
而不需要对捕获的变量进行可变访问的闭包实现了Fn。

2.1 Fn:不可变借用

fn main() {//闭包的捕获//不可变借用let s = String::from("hello");let print_s = || {println!("{}", s); // 不可变借用s};print_s();println!("{}", s); // 可以再次使用s
}

在这里插入图片描述

2.2 FnMut:可变借用

fn main() {//可变借用let mut s = String::from("hello");let mut append_world = || {s.push_str(" world"); // 可变借用sprintln!("{}", s); // 可变借用s};append_world();println!("{}", s); // 这里可以再次使用s}

在这里插入图片描述

2.3 FnOnce:获取所有权

fn main() {//获取所有权//获取所有权的闭包//将变量的所有权转移到闭包中,将变量返回let s = String::from("hello");// let consume_s = || {//     println!("{}", s);// std::mem::drop(s); // 获取s的所有权// };//对于非Copy类型的变量,闭包会捕获变量的所有权let consume_s = || s;consume_s();// println!("{}", s); // 错误!s的所有权已被移动let x = vec![1, 2, 3];let takes_ownership = || x; // 获取x的所有权let y = takes_ownership(); // 调用闭包,获取x的所有权println!("y = {:?}", y); // 可以使用y// 这里不能再使用x// println!("{:?}", x); // 错误!x的所有权已被移动//对于Copy类型的变量,闭包会捕获变量的不可变借用let x = 4;let get_number = || x; // 捕获x的不可变借用let y = get_number(); // 调用闭包,获取x的不可变借用println!("x = {}, y = {}", x, y); // 可以使用x和y}

非Copy类型变量,闭包获取其所有权
在这里插入图片描述

Copy类型的变量,闭包会捕获变量的不可变借用
在这里插入图片描述

2.4 move关键字

使用move关键字强制闭包获取变量的所有权:

fn main() {// 闭包捕获变量的所有权// 使用move关键字强制闭包获取变量的所有权let print_s = move || {println!("{}", s);};print_s();println!("{}", s); // 错误!s的所有权已移动到闭包中
}

在这里插入图片描述

这在多线程编程中特别有用,可以确保数据安全地移动到新线程。

3. 闭包作为参数和返回值

3.1 闭包作为函数参数

//定义泛型函数,F可以是闭包函数
//对F进行泛型约束,必须实现Fn(i32)
fn apply<F>(f: F, x: i32) -> i32 where F: Fn(i32) -> i32 {//返回闭包函数的调用f(x)
}fn main() {//创建闭包let double = |x| x * 2;//闭包作为参数传进去apply函数println!("{}", apply(double, 5)); // 输出10
}

在这里插入图片描述

3.2 闭包作为结构体字段

//闭包作为结构体字段
//泛型结构体,约束为闭包函数
//泛型闭包
struct Cacher<T> where T: Fn(i32) -> i32 {//calculation属于闭包函数,作为结构体的字段calculation: T,value: Option<i32>,
}impl<T> Cacher<T> where T: Fn(i32) -> i32 {fn new(calculation: T) -> Cacher<T> {Cacher {calculation,value: None,}}//结构体方法fn value(&mut self, arg: i32) -> i32 {match self.value {Some(v) => v,None => {let v = (self.calculation)(arg);self.value = Some(v);v}}}
}fn main() {let mut cacher = Cacher::new(|x| x * 2);let result1 = cacher.value(10);println!("Result for input 10: {}", result1);let result2 = cacher.value(10); // This should use the cached valueprintln!("Result for input 10 (cached): {}", result2);let result3 = cacher.value(20); // This will compute a new valueprintln!("Result for input 20: {}", result3);
}

在这里插入图片描述

3.3 闭包作为函数返回值

也可以返回闭包,因为闭包的大小在编译时未知,需要使用 trait 对象或 impl Trait 语法:

//闭包作为返回值
//注意语法格式
//返回一个闭包函数
fn returns_closure() -> impl Fn(i32) -> i32 {|x| x + 1
}
fn main() {let closure = returns_closure();println!("closure: {}", closure(1));
}

在这里插入图片描述

或者使用 Box:

// 闭包作为返回值
// 注意语法格式
// 返回一个闭包函数
fn returns_closure() -> impl Fn(i32) -> i32 {|x| x + 1
}// 使用box
// 使用box有性能消耗
fn returns_closure_box() -> Box<dyn Fn(i32) -> i32> {Box::new(|x| x + 1)
}fn main() {let closure = returns_closure();println!("closure: {}", closure(1));let closure_box = returns_closure_box();println!("closure_box: {}", closure_box(1));
}

在这里插入图片描述

4. 闭包的实际应用案例

4.1 缓存/记忆化模式

使用闭包实现缓存:

//使用闭包做个缓存系统
//闭包作为结构体字段
//泛型结构体,约束为闭包函数
//泛型闭包
struct Cacher<T> where T: Fn(i32) -> i32 {//calculation属于闭包函数,作为结构体的字段calculation: T,value: Option<i32>,
}impl<T> Cacher<T> where T: Fn(i32) -> i32 {fn new(calculation: T) -> Cacher<T> {Cacher {calculation,value: None,}}//结构体方法fn value(&mut self, arg: i32) -> i32 {match self.value {Some(v) => v,None => {let v = (self.calculation)(arg);self.value = Some(v);v}}}
}fn main() {let mut cacher = Cacher::new(|x| x * 2);let result1 = cacher.value(10);println!("Result for input 10: {}", result1);let result2 = cacher.value(10); // This should use the cached valueprintln!("Result for input 10 (cached): {}", result2);let result3 = cacher.value(20); // This will compute a new valueprintln!("Result for input 20: {}", result3);
}

在这里插入图片描述

5. 高级闭包技巧

5.1 闭包与生命周期

当闭包捕获引用时,需要考虑生命周期:

//闭包中的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() { x } else { y }
}fn main() {let string1 = String::from("long string is long");let result;{let string2 = String::from("short");let closure = || longest(&string1, &string2);result = closure();}println!("The longest string is {}", result);
}

上面的代码会编译失败,因为string2的生命周期不够长。
在这里插入图片描述

解决方案是让闭包只返回string1:

//闭包中的生命周期
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() { x } else { y }
}fn main() {let string1 = String::from("long string is long");let result;{let string2 = String::from("short");let closure = || &string1; // 只捕获string1result = closure();}println!("The longest string is {}", result);
}

在这里插入图片描述

6. 性能考虑

闭包在Rust中的性能与普通函数相当,因为:
闭包不进行堆分配,没有运行时开销(不像其他语言的闭包可能需要在堆上分配,除非使用Box)
编译器可以内联闭包调用
捕获环境的闭包通常会被编译器优化

7. 总结

Rust的闭包是一个强大而灵活的特性,它:
可以捕获环境中的变量
有三种捕获方式(Fn、FnMut、FnOnce)
性能与普通函数相当
广泛应用于迭代器、线程、回调等场景
可以与泛型、trait对象等Rust特性结合使用
掌握闭包的使用是成为Rust高级程序员的重要一步。通过本文的示例和实践,相信大家伙应该已经对Rust闭包有了深入的理解。
在实际开发中,多思考何时使用闭包能让代码更简洁、更富有表达力,同时也要注意闭包捕获变量的生命周期和所有权问题。

http://www.xdnf.cn/news/474985.html

相关文章:

  • 嵌入式EasyRTC音视频实时通话SDK在工业制造领域的智能巡检/AR协作等应用
  • 【Linux】Shell脚本中向文件中写日志,以及日志文件大小、数量管理
  • 小波变换+注意力机制成为nature收割机
  • 【设计模式】- 结构型模式
  • MySQL的存储过程
  • C语言进阶-数组和函数
  • 青少年编程与数学 02-019 Rust 编程基础 15课题、错误处理
  • Python连接redis
  • XML简要介绍
  • 模拟jenkins+k8s自动化部署
  • 济南超算研究所面试问题
  • MAX6749KA-T硬件看门狗调试
  • 医学影像系统性能优化与调试技术:深度剖析与实践指南
  • 一台入网的电脑有6要素, 机器名,mac,ip,俺码,网关,dns,分别有什么作用
  • ReinboT:通过强化学习增强机器人视觉-语言操控能力
  • 微信小程序:封装request请求、解决请求路径问题
  • Vue3 加快页面加载速度 使用CDN外部库的加载 提升页面打开速度 服务器分发
  • 云计算与大数据进阶 | 26、解锁云架构核心:深度解析可扩展数据库的5大策略与挑战(上)
  • Kubernetes 1.28 无 Docker 运行时环境下的容器化构建实践:Kaniko + Jenkins 全链路详解
  • 学习threejs,使用Physijs物理引擎,各种constraint约束限制
  • 分布式锁: Redisson 实现分布式锁的原理与技术细节
  • 前端下载ZIP包方法总结
  • 前端取经路——量子UI:响应式交互新范式
  • 第二天的尝试
  • Java + 鸿蒙双引擎:ZKmall开源商城如何定义下一代B2C商城技术标准?
  • 临床决策支持系统的提示工程优化路径深度解析
  • 【SpringBoot】从零开始全面解析SpringMVC (二)
  • TensorFlow/Keras实现知识蒸馏案例
  • Pyhton训练营打卡Day27
  • virtualbox虚拟机中的ubuntu 20.04.6安装新的linux内核5.4.293 | 并增加一个系统调用 | 证书问题如何解决