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

rust踩雷笔记3——生命周期的理解

目录

      • 概念和基本使用
        • 一个例子彻底理解最基本的内容
      • 一个例子理解函数签名为什么要有生命周期标注
      • ⭐️能不能对编译器蒙混过关?

生命周期是rust中最难的概念——鲁迅

这一块内容即便是看rust圣经,第一遍也有点懵。今天早上二刷突然有了更直观的认识,记录一下。

概念和基本使用

生命周期就是应用的有效作用域,它的主要作用是避免悬垂引用。

悬垂引用的典型例子:

{let y;{let x = 1;y = &x;}println!("y: {}", y);
}

简而言之就是y引用的变量在y被使用之前就释放了。

我们通过肉眼检查上面代码,会发现x的生命周期没有延续到y被使用的时候,所以会发现问题。rust为了安全做出了很多约束,所以这里即便是我们不用肉眼观察,也能发现问题。

一个例子彻底理解最基本的内容

让我们直接切入使用,来体会生命周期的用法。

首先,就算你什么都不做,编译器大多数可以自动推导出生命周期。

如果你要手动标注声明周期,那么就加'a符号,'是重点,a的话可以替换。

看如下代码:

let y;
{let x = 1;y = &x;
}
println!("y: {}", y);

很显然发生了悬垂引用,报错信息:
x does not live long enough, borrowed value does not live long enough
x的生命周期在花括号内,想要避免悬垂引用,需要将x的生命周期延长到y的位置

方法一✔️:

let x = 1;
let y;
{y = &x;
}
println!("y: {}", y);

这样肯定就没错了,x作为被引用的变量,生命周期比引用它的y长

方法二❎:
不改变代码,只是添加生命周期引用?

当然不行!

生命周期引用只是为了向编译器做出说明,如果加了'a的话,就说明引用的作用域大于等于’a。如果对多个引用加同一个’a,说明这些引用的作用域都大于等于’a。可以看到,这是一种约束条件;
生命周期标注’a并不改变引用的真实生命周期,只是告诉编译器,当不满足’a表示的约束条件时,就报错。

⭐️一个例子体会生命周期的作用——让编译器检查约束条件

let x = 5;
{let y = &x;
}

这个代码肯定没有问题,被引用的x比引用y活得久。

那如果此时我加入生命周期约束

let x = 5;
{let y: &'static i32 = &x;
}

这是在告诉编译器y具有全局生命周期,但实际上y的生命周期在花括号内,上述代码没有悬垂引用。

那你觉得此时,编译器报不报错?
当然会!记住两个原则:

  1. 生命周期标注不改变引用的真实生命周期(比如上述的y还是在花括号内)
  2. 编译器会相信你标注的生命周期,而不是引用真实的生命周期

没错,当你标注’static起,编译器就认为y可以活全局,那么此时它检查x和y的约束关系,就会按照这个来。所以报错信息是:
x does not live long enough, borrowed value does not live long enough
编译器觉得x不能活全局,所以y活全局的话,会发生悬垂引用。

⚠️注意:
问:上述代码有没有发生悬垂引用?
答:没有。
问:'static有没有改变y的真实生命周期?
答:没有。
问:那为啥报了悬垂引用的错误?
答:因为编译器相信你的标注,从而它认为y就是活全局的。

一个例子理解函数签名为什么要有生命周期标注

摘自rust圣经的一段代码:

fn main() {let string1 = String::from("abcd");let string2 = "xyz";let result = longest(string1.as_str(), string2);println!("The longest string is {}", result);
}
fn longest(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y}
}

这个程序会报错:
help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from x or y

我们来分析一下,当longest被调用的时候,x引用string1,y引用string2(严格讲string2本身是&str)。result因为获取返回值,所以是引用string1或者string2,那么问题来了,rust因为严格的安全检查,此时要检查是否有悬垂引用,即检查是否有被引用的变量生命周期大于引用变量。

可是编译期间是无法获知result会引用哪个字符串的,编译器这个大聪明就无法检查是否有悬垂引用。

rust圣经说了,改成这个就不会有问题了:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}

记住分析:编译器会认为x和y生命周期都不小于’a,并且’a的大小就是x和y中生命周期的交集。所以相当于告诉了编译器,把具体引用传给返回值的时候,返回值的生命周期为x和y生命周期的交集。

如下例子深入理解:
(依旧摘自rust圣经)

fn main() {let string1 = String::from("long string is long");let result;{let string2 = String::from("xyz");result = longest(string1.as_str(), string2.as_str());}println!("The longest string is {}", result);
}

分析:我们告诉编译器的结论是,'a的生命周期是string1和string2的交集,也就是string2的生命周期;因此result生命周期等于string2的生命周期。

然后,编译器根据我们的结论,去检查代码有没有悬垂引用的风险,虽然上述函数调用,会使得result引用的是string1,这样一看没有悬垂引用。但是编译期间不知道result会引用谁,此时编译器一看,如果引用的是string2,那么悬垂引用就会发生,所以就会报错。

由于此例子中result只会引用string1,所以代码也许可以这样改:

fn longest<'a>(x: &'a str, y: &str) -> &'a str {x
}

就不会出错了。

先写这么多,剩下的结合实践深入了解。

我又回来了,因为发现一个更好做说明的例子:

⭐️能不能对编译器蒙混过关?

这个标题什么意思呢,我们再把rust圣经中一段代码摘过来

fn main() {let string1 = String::from("long string is long");let result;{let string2 = String::from("xyz");result = longest(string1.as_str(), string2.as_str());}println!("The longest string is {}", result);
}
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}

刚刚说了,这个生命周期标注会让编译器意识到有悬垂引用风险,于是拒绝代码执行

safety这个单词被发明出来前,还有个单词表示安全,叫rusty——鲁迅

这时候咱可能不爽了,你看明明string1更长,longest只会返回对string1的引用,肉眼就能发现没有悬垂引用,为什么编译器这个大聪明一定要去检查string2呢?

不行,不能惯着编译器,我们修改代码:

fn longest<'a>(x: &'a str, y: &str) -> &'a str {if x.len() > y.len() {x} else {y}
}

我直接把y的’a标注删掉,看你怎么检查string2.

结果编译器也是倔强,反手来了个新错误:
explicit lifetime required in the type of y, lifetime 'a required
也就是说没有标注编译器就没办法检查悬垂引用,没办法检查就会直接报错。

那我继续改:

fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {if x.len() > y.len() {x} else {y}
}

改成’b总可以了吧,还是不行:
lifetime may not live long enough, consider adding the following bound: 'b: 'a
'b: 'a表示’b生命周期大于’a。

那继续改:

fn longest<'a, 'b: 'a>(x: &'a str, y: &'b str) -> &'a str {if x.len() > y.len() {x} else {y}
}

结果依旧是报错:
string2 does not live long enough

你会发现,

  • 不标注y生命周期的时候,编译器因为无法检查悬垂引用而报错;
  • 给y标注一个’b后,因为函数返回值是’a,编译器无法判断’b和’a的关系,也就无法检查悬垂引用;
  • 标注’a: 'b或者’b: 'a都可以表示两者关系,但是我们只能选没有悬垂引用的那种;
  • longest返回值是一个引用,它可能会引用y,所以这里只能是’b: 'a,如果你标注’a: 'b,说明你告诉编译器返回值获得比y久,那么编译器会相信你说的,就直接因为可能发生悬垂引用报错;
  • 那你正好标注’b: 'a,但是编译器发现和事实不符,于是报错。

⚠️总结:

  1. 你给编译器说什么,编译器就信什么,所以你不能乱说,要标注正确的生命周期,以及不同生命周期的关系。如果编译器从你的标注就感觉到了悬垂引用,就会报错;
  2. 你标注了什么,不代表真实的生命周期就是什么,你的标注只是告诉编译器你希望的事实,如果实际情况不满足标注的约束,编译器就会报错。

简版:
你的标注若有悬垂引用,哪怕实际上没有,编译器也会报错;
你的标注若与事实不符,标注没有悬垂引用而实际有或者有风险发生,编译器报错。

所以是否报错要看:
标注是否正确+事实是否符合正确的标注约束

相关文章:

rust踩雷笔记3——生命周期的理解

目录 概念和基本使用一个例子彻底理解最基本的内容 一个例子理解函数签名为什么要有生命周期标注⭐️能不能对编译器蒙混过关&#xff1f; 生命周期是rust中最难的概念——鲁迅 这一块内容即便是看rust圣经&#xff0c;第一遍也有点懵。今天早上二刷突然有了更直观的认识&…...

windows权限维持—黄金白银票据隐藏用户远控RustDeskGotoHttp

windows权限维持—黄金白银票据&隐藏用户&远控&RustDesk&GotoHttp 1. 前置1.1. 初始问题1.1.1. 解决办法 2. 隐藏用户2.1. 工具原理2.2. 案例操作2.2.1. 单机添加用户2.2.1.1. 工具添加用户2.2.1.2. 工具查看隐藏用户2.2.1.3. 本地查看隐藏用户 2.2.2. 域内添加…...

vscode conda activate激活环境出错

vscode conda activate 出错 conda-script.py: error: argument COMMAND: invalid choice: ‘activate’ To initialize your shell, run$ conda init <SHELL_NAME>Currently supported shells are:- bash- fish- tcsh- xonsh- zsh- powershellSee conda init --help f…...

信息与通信工程面试准备——数学知识|正态分布|中心极限定理

目录 正态分布 正态分布的参数 正态分布的第一个参数是均值 正态分布的第二个参数是标准差SD 所有正态分布的共同特征 标准正态分布&#xff1a;正态分布的特例 中心极限定理 理解定义 示例# 1 示例# 2 知道样本均值总是正态分布的实际含义是什么&#xff1f; 正态分…...

Mybatis多表查询与动态SQL的使用

目录 1. Mybatis多表查询 1.1 添加文章表实体类 1.2 文章Interface 1.3 文章.xml 1.4 lombok的toString()有关对象打印的说明 1.5 场景: 一个用户查询多篇文章 2. 复杂情况: 动态SQL的使用 2.1 为什么要使用动态SQL? 2.2 <if>标签 2.3 <trim>标签 2.4 <where&g…...

url 和 uri 有什么区别?

URL&#xff08;Uniform Resource Locator&#xff09;和URI&#xff08;Uniform Resource Identifier&#xff09;是两个与网络资源定位和标识相关的概念&#xff0c;它们有一些区别&#xff0c;但也存在一些重叠。 URI&#xff08;Uniform Resource Identifier&#xff09;是…...

HCIP VLAN实验

VLAN实验 拓扑图配置和分析分析配置LSW1LSW2R1 测试dhcp获取ipICMP测试 拓扑图 配置和分析 分析 从题目来看&#xff0c;因为 pc 1 3都是vlan2而且还是不同网段&#xff0c;pc 2 4 5 6在同一网段&#xff0c;所以可以将pc 1 2 5 4 6分在一个网段 pc4不通5 6 &#xff0c;那就…...

无涯教程-Perl - waitpid函数

描述 该函数等待ID为PID的子进程终止,返回已故进程的进程ID。如果PID不存在,则返回-1。进程的退出状态包含在$?中。 可以将标志设置为各种值,这些值等于waitpid()UNIX系统调用使用的值。 FLAGS的值为0应该在支持进程的所有操作系统上工作。 语法 以下是此函数的简单语法- …...

Redis之缓存雪崩、缓存击穿、缓存穿透问题

文章目录 前言一、缓存雪崩1.1、原因分析2.2、常用解决方案 二、缓存击穿2.1、原因分析2.2、常用解决方案2.2.1、使用互斥锁2.2.2、逻辑过期方案2.3、方案对比 三、缓存穿透3.1、原因分析3.2、解决方案3.2.1、缓存空对象3.2.3、布隆过滤3.3、方案对比 总结 前言 本文谈谈Redis…...

九五从零开始的运维之路(其三十五)

文章目录 前言一、概述1.概念2.组成3.特点4.工作原理5.优点&#xff1a; 二、各节点及其ip地址三、构建MHA1.ssh免密登录2.构建mysql主从复制&#xff08;一&#xff09;安装mariadb数据库并启动&#xff08;二&#xff09;master服务器&#xff08;三&#xff09;slave服务器&…...

5G科技防汛,助力守护一方平安

“立秋虽已至&#xff0c;炎夏尚还在”&#xff0c;受台风席卷以及季节性影响全国多地正面临强降水的严峻挑战。“落雨又顺秋&#xff0c;绵绵雨不休”&#xff0c;正值“七下八上” 防汛关键时期&#xff0c;贵州省水文水资源局已全面进入备战状态。 为确保及时响应做好防汛抢…...

用easyui DataGrid编辑树形资料

easyui显示编辑树形资料有TreeGrid元件&#xff0c;但是这个元件的vue版本和react版本没有分页功能。virtual scroll功能也表现不佳。 我用DataGrid来处理。要解决的问题点&#xff1a; &#xff08;1&#xff09;如何显示成树形。即&#xff0c;子节点如何有缩进。 先计算好…...

Azure存储账户

存储账户的概念 Azure存储账户是Azure提供的一种云存储解决方案&#xff0c;用于存储和访问各种类型的数据&#xff0c;包括文件、磁盘、队列、表格和Blob&#xff08;二进制大对象&#xff09;数据。存储账户可以基于访问模式和冗余需求来选择不同的类型&#xff0c;以满足应…...

数字人服装布料解算技术服务,让数字人驱动更真实

一个数字人通过三维建模、骨骼绑定、表情绑定后&#xff0c;对于数字人有两种使用场景&#xff0c;可以使用动捕设备实时驱动&#xff0c;将静态的3D模型结合动捕设备实时“活”起来。数字人通过动捕设备实时驱动的过程&#xff0c;则是基于实时布料毛发解算方案进行技术处理的…...

达梦数据库安装与初始化超详细教程

陈老老老板&#x1f9b8; &#x1f468;‍&#x1f4bb;本文专栏&#xff1a;国产数据库-达梦数据库&#xff08;主要讲一些达梦数据库相关的内容&#xff09; &#x1f468;‍&#x1f4bb;本文简述&#xff1a;本文讲一下达梦数据库的下载与安装教程&#xff08;Windows版&am…...

vue输入框只能输入数字类型,禁止输入和粘贴e

js怎么去除1e里面e 方法一&#xff1a;使用 Number() 函数将科学计数法表示的字符串转换为数字。然后&#xff0c;使用 toString() 方法将其转换回字符串形式&#xff0c;这样就会自动移除科学计数法中的 "e" var num 1e10; // 科学计数法表示的数字 var numStr …...

金盘 微信管理平台 getsysteminfo 未授权访问漏洞[2023-HW]

金盘 微信管理平台 getsysteminfo 未授权访问漏洞 一、漏洞描述二、漏洞影响三、网络测绘四、漏洞复现小龙POC检测: 五、 修复建议 免责声明&#xff1a;请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后…...

02-前端基础第二天-HTML5

01-HTML标签&#xff08;下&#xff09;导读 目标&#xff1a; 能够书写表格能够写出无序列表能够写出3~4个常用input表单类型能够写出下拉列表表单能够使用表单元素实现注册页面能够独立查阅W3C文档 目录&#xff1a; 表格标签列表标签表单标签综合案例查阅文档 02-表格标…...

i18n 配置vue项目中英文语言包(中英文转化)

一、实现效果 二、下载插件创建文件夹 2.1 下载cookie来存储 npm install --save js-cookienpm i vue-i18n -S 2.2 封装组件多页面应用 2.3 创建配置语言包字段 三、示例代码 3.1 main.js 引用 i18n.js import i18n from ./lang// 实现语言切换:i18n处理element&#xff0c…...

NFTScan NFT API 在 DID Protocol 开发中的应用

自互联网发展以来&#xff0c;Web2.0 时代产生了网络社会&#xff0c;社会已经不再局限于地理边界&#xff0c;而 Web 3.0 引入了去中心化的理念&#xff0c;强调个体数据隐私和可信互操作性。在这个新的时代中&#xff0c;去中心化身份&#xff08;Decentralized Identifier 即…...

React 第五十五节 Router 中 useAsyncError的使用详解

前言 useAsyncError 是 React Router v6.4 引入的一个钩子&#xff0c;用于处理异步操作&#xff08;如数据加载&#xff09;中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误&#xff1a;捕获在 loader 或 action 中发生的异步错误替…...

vscode(仍待补充)

写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh&#xff1f; debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务

通过akshare库&#xff0c;获取股票数据&#xff0c;并生成TabPFN这个模型 可以识别、处理的格式&#xff0c;写一个完整的预处理示例&#xff0c;并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务&#xff0c;进行预测并输…...

【配置 YOLOX 用于按目录分类的图片数据集】

现在的图标点选越来越多&#xff0c;如何一步解决&#xff0c;采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集&#xff08;每个目录代表一个类别&#xff0c;目录下是该类别的所有图片&#xff09;&#xff0c;你需要进行以下配置步骤&#x…...

CMake 从 GitHub 下载第三方库并使用

有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)

骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术&#xff0c;它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton)&#xff1a;由层级结构的骨头组成&#xff0c;类似于人体骨骼蒙皮 (Mesh Skinning)&#xff1a;将模型网格顶点绑定到骨骼上&#xff0c;使骨骼移动…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)

本期内容并不是很难&#xff0c;相信大家会学的很愉快&#xff0c;当然对于有后端基础的朋友来说&#xff0c;本期内容更加容易了解&#xff0c;当然没有基础的也别担心&#xff0c;本期内容会详细解释有关内容 本期用到的软件&#xff1a;yakit&#xff08;因为经过之前好多期…...

云原生安全实战:API网关Kong的鉴权与限流详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关&#xff08;API Gateway&#xff09; API网关是微服务架构中的核心组件&#xff0c;负责统一管理所有API的流量入口。它像一座…...

Linux 下 DMA 内存映射浅析

序 系统 I/O 设备驱动程序通常调用其特定子系统的接口为 DMA 分配内存&#xff0c;但最终会调到 DMA 子系统的dma_alloc_coherent()/dma_alloc_attrs() 等接口。 关于 dma_alloc_coherent 接口详细的代码讲解、调用流程&#xff0c;可以参考这篇文章&#xff0c;我觉得写的非常…...