Rust 学习笔记:闭包

news2025/6/1 22:16:00

Rust 学习笔记:闭包

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

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 移到给新线程的闭包中,这样引用才有效。

将捕获的值移出闭包和 Fn Traits

一旦闭包从定义闭包的环境中捕获了引用或捕获了值的所有权,闭包体中的代码将定义在稍后求值闭包时对引用或值的处理。

闭包体可以执行以下任何操作:将捕获的值移出闭包,改变捕获的值,既不移动也不改变值,或者从环境开始不捕获任何值。

闭包从环境中捕获和处理值的方式会影响闭包实现的 trait,而 trait 就是函数和结构如何指定它们可以使用的闭包类型。闭包将以一种附加的方式自动实现一个、两个或所有这三个 Fn trait,具体取决于闭包的主体如何处理这些值:

  1. FnOnce:应用于可以调用一次的闭包。所有闭包都至少实现了这个特性,因为所有闭包都可以被调用。将捕获值移出其体的闭包只会实现 FnOnce,而不会实现其他 Fn trait,因为它只能被调用一次。
  2. FnMut:应用于闭包,闭包不会将捕获的值移出其主体,但可能会改变捕获的值。这些闭包可以被多次调用。
  3. Fn:适用于不会将捕获值移出其主体且不会改变捕获值的闭包,以及不从其环境中捕获任何值的闭包。这些闭包可以被多次调用而不会改变它们的环境,这在并发调用闭包多次的情况下非常重要。

让我们看一下 Option<T> 的 unwrap_or_else 方法的定义:

impl<T> Option<T> {
    pub fn unwrap_or_else<F>(self, f: F) -> T
    where
        F: FnOnce() -> T
    {
        match self {
            Some(x) => x,
            None => f(),
        }
    }
}

T 是泛型类型,表示 Option 的某些变体中的值的类型,也是 unwrap_or_else 函数的返回类型。

unwrap_or_else 函数具有额外的泛型类型参数 F,它是我们在调用 unwrap_or_else 时提供的闭包。

在泛型类型 F 上指定的 trait 绑定是 FnOnce() -> T,这意味着 F 必须能够被调用一次,不接受参数,并返回一个 T。在函数体中,如果 Option 是 Some,则不会调用 f。如果 Option 为 None,则 f 将被调用一次。因为所有闭包都实现了 FnOnce,所以 unwrap_or_else 函数接受所有三种闭包。

注意:如果我们想要做的事情不需要从环境中捕获值,我们可以使用函数名而不是闭包。
例如,如果 Option<Vec<T>> 值为 None,则可以调用 unwrap_or_else(Vec::new) 来获得一个新的空 vector。编译器自动实现任何一个适用于函数定义的 Fn traits。

现在让我们看一下在切片上定义的标准库方法 sort_by_key,看看它与 unwrap_or_else 有什么不同,以及为什么 sort_by_key 使用 FnMut 而不是 FnOnce 作为 trait 绑定。闭包以引用的形式获得一个参数,并返回排序后的结果。

在下面的示例中,我们有一个矩形实例列表,我们使用 sort_by_key 按它们的 width 属性从低到高排序:

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let mut list = [
        Rectangle { width: 10, height: 1 },
        Rectangle { width: 3, height: 5 },
        Rectangle { width: 7, height: 12 },
    ];

    list.sort_by_key(|r| r.width);
    println!("{list:#?}");
}

运行结果:

在这里插入图片描述

sort_by_key 被定义为接受 FnMut 闭包的原因是,它多次调用闭包:对片中的每个项调用一次。闭包 |r| r.width 不会捕获、改变或从其环境中移出任何东西。

相反,下面的代码展示了一个闭包示例,它只实现了 FnOnce trait,因为它将一个值移出了环境。编译器不允许我们使用 sort_by_key 闭包。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let mut list = [
        Rectangle { width: 10, height: 1 },
        Rectangle { width: 3, height: 5 },
        Rectangle { width: 7, height: 12 },
    ];

    let mut sort_operations = vec![];
    let value = String::from("closure called");

    list.sort_by_key(|r| {
        sort_operations.push(value);
        r.width
    });
    println!("{list:#?}");
}

这段代码试图通过将一个字符串从闭包的环境压入数组来完成计数。闭包捕获 value,然后通过将 value 的所有权转移给 sort_operations 数组,将 value 移出闭包。这个闭包只可以被调用一次,尝试第二次调用它将不起作用,因为 value 已经没有对字符串的所有权了。因此,这个闭包只实现了 FnOnce。当我们尝试编译这段代码时,会得到这样的错误:不能将值移出闭包,因为闭包必须实现 FnMut。

在这里插入图片描述

错误指向闭包体中将值移出环境的行。要解决这个问题,我们需要更改闭包体,使其不会将值移出环境。在环境中保留一个计数器并在闭包体中增加其值是计算闭包调用次数的更直接的方法。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

fn main() {
    let mut list = [
        Rectangle { width: 10, height: 1 },
        Rectangle { width: 3, height: 5 },
        Rectangle { width: 7, height: 12 },
    ];

    let mut num_sort_operations = 0;
    list.sort_by_key(|r| {
        num_sort_operations += 1;
        r.width
    });
    println!("{list:#?}, sorted in {num_sort_operations} operations");
}

修改后的闭包可以与 sort_by_key 一起工作,因为它只捕获对 num_sort_operations 计数器的可变引用,因此可以多次调用。

在定义或使用使用闭包的函数或类型时,Fn Traits 非常重要。在下一节中,我们将讨论迭代器,许多迭代器方法都接受闭包参数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2392957.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c# 获取电脑 分辨率 及 DPI 设置

using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices;/// <summary> /// 这个可以 /// </summary> class Program {static void Main(){//设置DPI感知try{SetProcessDpiAwareness(…

低代码开发模式下的应用交付效率优化:拖拽式交互机制研究

低代码开发平台凭借其可视化操作、快速构建、灵活扩展等核心特性&#xff0c;正在成为推动企业数字化转型的重要工具。 拖拽式开发&#xff0c;降低技术门槛 &#xff1a;图形化界面与模块化组件&#xff0c;用户无需编写复杂代码&#xff0c;只需通过简单的拖拽即可完成应用搭…

STP配置

由于我们演示的是STP 但是华为交换机默认的都是MSTP所以要换到STP以下是方法 STP mode &#xff1f; 查看模式 STP mode stp 选择stp 换好了后配置交换机优先级 [SWA]stp priority 4096 Apr 15 2013 16:15:33-08:00 SWA DS/4/DATASYNC_CFGCHANGE:OID 1.3.6.1.4.1.2011.5…

Linux操作系统 使用共享内存实现进程通信和同步

共享内存使用 //main.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include <sys/shm.h> #include <string.h> int main() {int shmidshmget((key_t)1234,256,IPC_CREAT|0600);assert(shmid!-1);…

如何优化微信小程序中渲染带有图片的列表(二进制流存储方式的图片存在本地数据库)

方法一&#xff1a;对列表的获取进行分页处理 实现方法&#xff1a; 前端请求&#xff08;需要向后端传两个参数&#xff0c;pageIndex是获取第几页是从0开始&#xff0c;pageSize是这一页需要获取多少个数据&#xff09; 后端接口实现&#xff08;因为这里是通过参数拼接请求…

尝鲜纯血鸿蒙,华为国际版本暂时不支持升级。如mateX6 国际版?为什么不支持?什么时候支持?

一&#xff1a;mateX6 国际版支持鸿蒙吗&#xff1f; 不支持 二&#xff1a;华为国际版支持鸿蒙吗&#xff1f; 不支持 三&#xff1a;华为国际版什么时候支持&#xff1f; 2025年预期可以支持。请耐心等待。 三&#xff1a;国际版为什么不支持&#xff1f; EMUI 采用AO…

[科研实践] VS Code (Copilot) + Overleaf (使用 Overleaf Workshop 插件)

科研圈写文档常用 Latex 环境&#xff0c;尤其是 Overleaf 它自带的 AI 润色工具 Writefull 太难用了。如果能用本地的 CoPilot / Cursor 结合 Overleaf&#xff0c;那肯定超高效&#xff01; 于是我们找到了 VS Code 里的 Overleaf Workshop 插件。这里已经安装好了&#xff0…

从0开始学习R语言--Day12--泊松分布

今天我们来看一个很经典的回归模型&#xff1a;泊松分布。 泊松分布 我们一般会把泊松分布用于预测问题&#xff0c;比如想知道成年人每天接到的骚扰电话次数&#xff0c;医院每天的急诊病人等。但在一些方面&#xff0c;跟我们想的会有出入。例如你不能将其应用在预测下周你的…

工控机安装lubuntu系统

工控机安装lubuntu系统指南手册 1. 准备 1个8G左右的U盘 下载Rufus&#xff1a; Index of /downloads 下载lubuntu系统镜像&#xff1a; NJU Mirror Downloads – Lubuntu 下载Ventoy工具&#xff1a; Releases ventoy/Ventoy GitHub 下载后&#xff0c;解压&#…

视频监控汇聚平台EasyCVR安防小知识:如何通过视频融合平台解决信息孤岛问题?

一、项目背景与需求分析​ 随着数字化技术发展与网络带宽升级&#xff0c;视频技术应用场景不断拓展&#xff0c;视频监控、记录仪等多样化产品构建起庞大体系。但这些独立系统彼此割裂&#xff0c;形成信息孤岛。 在系统集成项目中&#xff0c;视频系统深度融合已成必然趋势…

在大型中实施访问控制 语言模型

大家读完觉得有帮助记得关注&#xff01;&#xff01;&#xff01; 抽象 在企业设置中&#xff0c;组织数据是隔离的、孤立的 并受到精心设计的访问控制框架的精心保护。 如果 LLM 对 siloed data serve 请求进行微调&#xff0c;用于下游任务&#xff0c; 来自具有不同访问权限…

Haption在危险、挑战性或受限环境中操作的情况提供了一种创新的遥操作解决方案

Haption Virtuose 6D TAO是一款拥有7个主动自由度的触觉设备&#xff0c;专为虚拟现实环境交互而设计。 它与Virtuose的一系列软件解决方案兼容&#xff0c;可让您直接在CAD软件中使用该设备进行装配仿真&#xff0c;并在3D游戏引擎中使用该设备&#xff0c;从而打造更加逼真的…

行为型:状态模式

目录 1、核心思想 2、实现方式 2.1 模式结构 2.2 实现案例 3、优缺点分析 4、适用场景 5、注意事项 1、核心思想 目的&#xff1a;将状态相关逻辑封装到独立的类中&#xff0c;消除复杂的条件分支&#xff0c;状态的切换由具体状态类自身管理 举例&#xff1a; 1>…

优雅草最新实战项目技术Discuz X3.5电子签约插件开发项目实施方案优雅草·卓伊凡

优雅草最新实战项目技术Discuz X3.5电子签约插件开发项目实施方案优雅草卓伊凡 一、项目概述 甲方需求&#xff1a;为现有Discuz X3.5系统集成电子签约功能&#xff0c;对接e签宝API&#xff0c;实现用户发起/签署合同、模板管理、签约记录查询等功能。 总预算&#xff1a;9,3…

基于本地化大模型的智能编程助手全栈实践:从模型部署到IDE深度集成学习心得

近年来&#xff0c;随着ChatGPT、Copilot等AI编程工具的爆发式增长&#xff0c;开发者生产力获得了前所未有的提升。然而&#xff0c;云服务的延迟、隐私顾虑及API调用成本促使我探索一种更自主可控的方案&#xff1a;基于开源大模型构建本地化智能编程助手。本文将分享我构建本…

实验设计与分析(第6版,Montgomery)第5章析因设计引导5.7节思考题5.8 R语言解题

本文是实验设计与分析&#xff08;第6版&#xff0c;Montgomery著&#xff0c;傅珏生译) 第5章析因设计引导5.7节思考题5.8 R语言解题。主要涉及方差分析&#xff0c;正态假设检验&#xff0c;残差分析&#xff0c;交互作用图。 (a) dataframe<-data.frame( Lightc(580,568…

引领机器人交互未来!MANUS数据手套解锁精准手部追踪

MANUS数据手套为机器人技术带来高精度手部追踪&#xff0c;助力实现人与机器的自然交互&#xff01;近年&#xff0c;越来越多客户希望利用这项技术精准操控机械臂、灵巧手和人形机器人&#xff0c;不断提升设备的智能化水平和交互体验。 MANUS数据手套是高精度人机交互设备&am…

源的企业级网络安全检测工具Prism X(棱镜X)

Prism X&#xff08;棱镜X&#xff09;是由yqcs团队自主研发的开源网络安全检测解决方案&#xff0c;专注于企业级风险自动化识别与漏洞智能探测。该工具采用轻量化架构与跨平台设计&#xff0c;全面兼容Windows、Linux及macOS操作系统&#xff0c;集成资产发现、指纹鉴别、弱口…

基于FPGA的二叉决策树cart算法verilog实现,训练环节采用MATLAB仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) MATLAB训练结果 上述决策树判决条件&#xff1a; 分类的决策树1 if x21<17191.5 then node 2 elseif x21>17191…

权限分配不合理如何影响企业运营?

“我们明明只给了她CRM的查看权限&#xff0c;怎么客户数据被删了&#xff1f;” “新员工入职三天了&#xff0c;HR系统权限还没开通&#xff0c;流程完全卡住&#xff01;” “上个月刚给项目经理配了财务权限&#xff0c;怎么又出乱子了&#xff1f;” 这些对话是否在你的…