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

Rust 学习笔记:闭包

Rust 学习笔记:闭包

  • Rust 学习笔记:闭包
    • 用闭包捕获环境
    • 闭包类型推断和注释
    • 捕获引用或移动所有权

Rust 学习笔记:闭包

Rust 的闭包是匿名函数,可以保存在变量中,也可以作为参数传递给其他函数。

可以在一个地方创建闭包,然后在其他地方调用闭包,以便在不同的上下文中对其进行计算。

与函数不同,闭包可以从定义它们的作用域捕获值。

用闭包捕获环境

实现一个赠送衬衫的程序。如果选择免费衬衫的人有他们最喜欢的颜色,他们就会得到那种颜色的衬衫。如果这个人没有指定最喜欢的颜色,他们就会得到公司目前最常用的颜色。

示例:

#[derive(Debug, PartialEq, Copy, Clone)]
enum ShirtColor {Red,Blue,
}struct Inventory {shirts: Vec<ShirtColor>,
}impl Inventory {fn giveaway(&self, user_preference: Option<ShirtColor>) -> ShirtColor {user_preference.unwrap_or_else(|| self.most_stocked())}fn most_stocked(&self) -> ShirtColor {let mut num_red = 0;let mut num_blue = 0;for color in &self.shirts {match color {ShirtColor::Red => num_red += 1,ShirtColor::Blue => num_blue += 1,}}if num_red > num_blue {ShirtColor::Red} else {ShirtColor::Blue}}
}fn main() {let store = Inventory {shirts: vec![ShirtColor::Blue, ShirtColor::Red, ShirtColor::Blue],};let user_pref1 = Some(ShirtColor::Red);let giveaway1 = store.giveaway(user_pref1);println!("The user with preference {:?} gets {:?}",user_pref1, giveaway1);let user_pref2 = None;let giveaway2 = store.giveaway(user_pref2);println!("The user with preference {:?} gets {:?}",user_pref2, giveaway2);
}

在 giveaway 方法中,我们将用户偏好作为 Option<ShirtColor> 类型的参数获取,并在 user_preference 上调用 unwrap_or_else 方法。Option<T> 上的 unwrap_or_else 方法由标准库定义。它接受一个参数:一个没有任何参数的闭包,返回一个值T(与存储在 Option<T> 的 Some 变体中的类型相同,在本例中为 ShirtColor)。如果 Option<T> 是 Some 变量,则 unwrap_or_else 返回 Some 中的值。如果 Option<T> 是 None 变量,则 unwrap_or_else 调用闭包并返回闭包返回的值。

我们指定闭包表达式 || self.most_stocked() 作为 unwrap_or_else 的参数。这是一个本身不接受参数的闭包(如果闭包有参数,它们将出现在两个竖条之间)。闭包的主体调用 self.most_stocked()。

闭包捕获对 self Inventory 实例的不可变引用,并将其与我们指定的代码一起传递给 unwrap_or_else 方法。一般函数是做不到这一点的。

闭包类型推断和注释

闭包通常不需要像 fn 函数那样注释参数的类型或返回值。

闭包通常很短,在这些有限的上下文中,编译器可以推断参数的类型和返回类型。

在极少数情况下,编译器也需要闭包类型注释。

为闭包注释类型类似于下面的例子。我们定义了一个闭包并将其存储在一个变量中。

    let expensive_closure = |num: u32| -> u32 {println!("calculating slowly...");thread::sleep(Duration::from_secs(2));num};

添加了类型注释后,闭包的语法看起来更类似于函数的语法。

以下四种定义的行为都是一致的:

// 函数定义
fn  add_one_v1   (x: u32) -> u32 { x + 1 }
// 完全带注释的闭包定义
let add_one_v2 = |x: u32| -> u32 { x + 1 };
// 从闭包定义中删除了类型注释
let add_one_v3 = |x|             { x + 1 };
// 删除了括号,括号是可选的,因为闭包体只有一个表达式
let add_one_v4 = |x|               x + 1  ;

对于闭包定义,编译器将为它们的每个形参及其返回值推断出一个具体类型。因为没有类型注解,我们可以用任何类型调用闭包。

示例:

    let example_closure = |x| x;let s = example_closure(String::from("hello"));let n = example_closure(5);

程序报错:

在这里插入图片描述

当我们第一次用 String 值调用 example_closure 时,编译器推断出 x 的类型和闭包的返回类型为 String。然后,这些类型被锁定在 example_closure 中的闭包中,当我们下一次尝试对相同的闭包使用不同的类型时,我们会得到一个类型错误。

捕获引用或移动所有权

闭包可以通过三种方式从其环境中获取值:不可变借用、可变借用和获取所有权。闭包将根据函数体如何处理捕获的值来决定使用哪一个。

示例 1:

fn main() {let list = vec![1, 2, 3];println!("Before defining closure: {list:?}");let only_borrows = || println!("From closure: {list:?}");println!("Before calling closure: {list:?}");only_borrows();println!("After calling closure: {list:?}");
}

定义了一个闭包 only_borrows,它捕获了一个指向名为 list 的 vector 的不可变引用,因为它只需要一个不可变引用就可以输出值。

这个例子还说明了变量可以绑定到闭包定义,稍后我们可以通过使用变量名和括号调用闭包,就好像变量名是函数名一样。

因为我们可以同时对 list 有多个不可变引用,所以在闭包定义之前、在闭包定义之后、在闭包被调用之前和在闭包被调用之后,都可以访问 list。这段代码编译、运行和输出:

在这里插入图片描述

示例 2:

fn main() {let mut list = vec![1, 2, 3];println!("Before defining closure: {list:?}");let mut borrows_mutably = || list.push(7);borrows_mutably();println!("After calling closure: {list:?}");
}

我们修改闭包体,使其向列表向量添加一个元素。

当定义 borrows_mutable 时,它捕获一个对 list 的可变引用。在闭包定义和闭包调用之间,不允许使用不可变借用来打印,因为当存在可变借用时不允许使用其他借用。

如果想强制闭包获得它在环境中使用的值的所有权,可以在参数列表之前使用 move 关键字。

当向新线程传递闭包以移动数据以使其归新线程所有时,此技术非常有用。

示例 3:

use std::thread;fn main() {let list = vec![1, 2, 3];println!("Before defining closure: {list:?}");thread::spawn(move || println!("From thread: {list:?}")).join().unwrap();
}

我们生成一个新线程,给线程一个闭包作为参数运行。尽管闭包体只需要一个不可变引用就能完成打印,但我们需要通过在闭包定义的开头放置 move 关键字来指定应该将列表移动到闭包中。

新线程可能在主线程的其余部分完成之前完成,或者主线程可能先完成。如果主线程保持 list 的所有权,但在新线程结束并删除list之前结束,则线程中的不可变引用将无效。因此,编译器要求将 list 移到给新线程的闭包中,这样引用才有效。


文章转载自:

http://YYp3JXdh.xkwrb.cn
http://cOq2Lq06.xkwrb.cn
http://1dPuRdKY.xkwrb.cn
http://3SOiquTr.xkwrb.cn
http://EJGkkVP8.xkwrb.cn
http://gfkMRsJH.xkwrb.cn
http://fgv5ifHh.xkwrb.cn
http://0bApYyKW.xkwrb.cn
http://DvIKO6UB.xkwrb.cn
http://hntqmjKq.xkwrb.cn
http://aDVohyT3.xkwrb.cn
http://S7fg6Ize.xkwrb.cn
http://pvjJYD5s.xkwrb.cn
http://QqPHj0aR.xkwrb.cn
http://C6p3k90P.xkwrb.cn
http://amjUgzuz.xkwrb.cn
http://7PkVJHlR.xkwrb.cn
http://oRpX4hxM.xkwrb.cn
http://DVKTl0mS.xkwrb.cn
http://j22sJV5Z.xkwrb.cn
http://v3tSJZby.xkwrb.cn
http://phtLagCs.xkwrb.cn
http://E8u1O12J.xkwrb.cn
http://75nBkBGO.xkwrb.cn
http://mpiDgk9y.xkwrb.cn
http://WrOvqsGD.xkwrb.cn
http://7sJSuToX.xkwrb.cn
http://QwCzAVo4.xkwrb.cn
http://6iXtGcVD.xkwrb.cn
http://ua6MbxLl.xkwrb.cn
http://www.dtcms.com/a/214276.html

相关文章:

  • 【Java学习笔记】final关键字
  • 蚂蚁集团 CTO 何征宇:AGI时代,海量数据带来的质变|OceanBase 开发者大会实录
  • GitHub 趋势日报 (2025年05月25日)
  • 刷机维修进阶教程-----没有开启usb调试 如何在锁定机型的拨号界面特殊手段来开启ADB
  • 大数据学习(121)-sql重点问题
  • C++ STL Queue容器使用详解
  • uniapp-商城-69-shop(2-商品列表,点击商品展示,商品的详情, vuex的使用,rich-text使用)
  • VMware Live Recovery 和 VMware Data Recovery区别
  • Ubuntu | NVIDIA 驱动、CUDA 与 cuDNN 的安装与配置 / 常见问题及解决方法
  • RAGFlow源码安装操作过程
  • 爬虫学习-Scrape Center spa2 超简单 JS 逆向
  • 利用python爬虫获取淘宝天猫商品评论封装API实战演示
  • Python 爬虫开发
  • YOLO 算法详解:实时目标检测的里程碑
  • Java 树形结构,根据名称搜索
  • 知识宇宙-职业篇:软件测试工程师
  • 【VASP】PBE与HSE之前的区别
  • moviepy视频添加中文水印
  • [yolov11改进系列]基于yolov11替换卷积神经网CNN为KANConv的python源码+训练源码
  • 谷歌Veo vs Sora:AI视频生成技术的巅峰对决
  • 【Unity3D】将自动生成的脚本包含到C#工程文件中
  • 前端安全直传MinIO方案
  • Spring Cloud Gateway 限流实践:基于 Redis 令牌桶算法的网关层流量治理
  • Visual Studio 调试中 PDB 与图像不匹配
  • springcloud---gateway
  • [攻防世界] easyphp writeup
  • 北京大学肖臻老师《区块链技术与应用》公开课:02-BTC-密码学原理
  • 【React】- React-RND 深度使用指南:实现自由拖拽、避坑受控陷阱!
  • Java—— 多线程 第一期
  • cursor/vscode连接低版本的系统(glibc<2.28)