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的生命周期在花括号内,上述代码没有悬垂引用。
那你觉得此时,编译器报不报错?
当然会!记住两个原则:
- 生命周期标注不改变引用的真实生命周期(比如上述的y还是在花括号内)
- 编译器会相信你标注的生命周期,而不是引用真实的生命周期
没错,当你标注’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,但是编译器发现和事实不符,于是报错。
⚠️总结:
- 你给编译器说什么,编译器就信什么,所以你不能乱说,要标注正确的生命周期,以及不同生命周期的关系。如果编译器从你的标注就感觉到了悬垂引用,就会报错;
- 你标注了什么,不代表真实的生命周期就是什么,你的标注只是告诉编译器你希望的事实,如果实际情况不满足标注的约束,编译器就会报错。
简版:
你的标注若有悬垂引用,哪怕实际上没有,编译器也会报错;
你的标注若与事实不符,标注没有悬垂引用而实际有或者有风险发生,编译器报错。
所以是否报错要看:
标注是否正确+事实是否符合正确的标注约束
相关文章:
rust踩雷笔记3——生命周期的理解
目录 概念和基本使用一个例子彻底理解最基本的内容 一个例子理解函数签名为什么要有生命周期标注⭐️能不能对编译器蒙混过关? 生命周期是rust中最难的概念——鲁迅 这一块内容即便是看rust圣经,第一遍也有点懵。今天早上二刷突然有了更直观的认识&…...
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 所有正态分布的共同特征 标准正态分布:正态分布的特例 中心极限定理 理解定义 示例# 1 示例# 2 知道样本均值总是正态分布的实际含义是什么? 正态分…...
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(Uniform Resource Locator)和URI(Uniform Resource Identifier)是两个与网络资源定位和标识相关的概念,它们有一些区别,但也存在一些重叠。 URI(Uniform Resource Identifier)是…...
HCIP VLAN实验
VLAN实验 拓扑图配置和分析分析配置LSW1LSW2R1 测试dhcp获取ipICMP测试 拓扑图 配置和分析 分析 从题目来看,因为 pc 1 3都是vlan2而且还是不同网段,pc 2 4 5 6在同一网段,所以可以将pc 1 2 5 4 6分在一个网段 pc4不通5 6 ,那就…...
无涯教程-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.优点: 二、各节点及其ip地址三、构建MHA1.ssh免密登录2.构建mysql主从复制(一)安装mariadb数据库并启动(二)master服务器(三)slave服务器&…...
5G科技防汛,助力守护一方平安
“立秋虽已至,炎夏尚还在”,受台风席卷以及季节性影响全国多地正面临强降水的严峻挑战。“落雨又顺秋,绵绵雨不休”,正值“七下八上” 防汛关键时期,贵州省水文水资源局已全面进入备战状态。 为确保及时响应做好防汛抢…...
用easyui DataGrid编辑树形资料
easyui显示编辑树形资料有TreeGrid元件,但是这个元件的vue版本和react版本没有分页功能。virtual scroll功能也表现不佳。 我用DataGrid来处理。要解决的问题点: (1)如何显示成树形。即,子节点如何有缩进。 先计算好…...
Azure存储账户
存储账户的概念 Azure存储账户是Azure提供的一种云存储解决方案,用于存储和访问各种类型的数据,包括文件、磁盘、队列、表格和Blob(二进制大对象)数据。存储账户可以基于访问模式和冗余需求来选择不同的类型,以满足应…...
数字人服装布料解算技术服务,让数字人驱动更真实
一个数字人通过三维建模、骨骼绑定、表情绑定后,对于数字人有两种使用场景,可以使用动捕设备实时驱动,将静态的3D模型结合动捕设备实时“活”起来。数字人通过动捕设备实时驱动的过程,则是基于实时布料毛发解算方案进行技术处理的…...
达梦数据库安装与初始化超详细教程
陈老老老板🦸 👨💻本文专栏:国产数据库-达梦数据库(主要讲一些达梦数据库相关的内容) 👨💻本文简述:本文讲一下达梦数据库的下载与安装教程(Windows版&am…...
vue输入框只能输入数字类型,禁止输入和粘贴e
js怎么去除1e里面e 方法一:使用 Number() 函数将科学计数法表示的字符串转换为数字。然后,使用 toString() 方法将其转换回字符串形式,这样就会自动移除科学计数法中的 "e" var num 1e10; // 科学计数法表示的数字 var numStr …...
金盘 微信管理平台 getsysteminfo 未授权访问漏洞[2023-HW]
金盘 微信管理平台 getsysteminfo 未授权访问漏洞 一、漏洞描述二、漏洞影响三、网络测绘四、漏洞复现小龙POC检测: 五、 修复建议 免责声明:请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后…...
02-前端基础第二天-HTML5
01-HTML标签(下)导读 目标: 能够书写表格能够写出无序列表能够写出3~4个常用input表单类型能够写出下拉列表表单能够使用表单元素实现注册页面能够独立查阅W3C文档 目录: 表格标签列表标签表单标签综合案例查阅文档 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,…...
NFTScan NFT API 在 DID Protocol 开发中的应用
自互联网发展以来,Web2.0 时代产生了网络社会,社会已经不再局限于地理边界,而 Web 3.0 引入了去中心化的理念,强调个体数据隐私和可信互操作性。在这个新的时代中,去中心化身份(Decentralized Identifier 即…...
ssc377d修改flash分区大小
1、flash的分区默认分配16M、 / # df -h Filesystem Size Used Available Use% Mounted on /dev/root 1.9M 1.9M 0 100% / /dev/mtdblock4 3.0M...
微信小程序 - 手机震动
一、界面 <button type"primary" bindtap"shortVibrate">短震动</button> <button type"primary" bindtap"longVibrate">长震动</button> 二、js逻辑代码 注:文档 https://developers.weixin.qq…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
初探Service服务发现机制
1.Service简介 Service是将运行在一组Pod上的应用程序发布为网络服务的抽象方法。 主要功能:服务发现和负载均衡。 Service类型的包括ClusterIP类型、NodePort类型、LoadBalancer类型、ExternalName类型 2.Endpoints简介 Endpoints是一种Kubernetes资源…...
mac 安装homebrew (nvm 及git)
mac 安装nvm 及git 万恶之源 mac 安装这些东西离不开Xcode。及homebrew 一、先说安装git步骤 通用: 方法一:使用 Homebrew 安装 Git(推荐) 步骤如下:打开终端(Terminal.app) 1.安装 Homebrew…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
