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

news/2024/12/29 18:20:20/

目录

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

生命周期是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. 你标注了什么,不代表真实的生命周期就是什么,你的标注只是告诉编译器你希望的事实,如果实际情况不满足标注的约束,编译器就会报错。

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

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


http://www.ppmy.cn/news/1042328.html

相关文章

springboot数据库密码加密的配置方法_Java

前言 由于系统安全的考虑&#xff0c;配置文件中不能出现明文密码的问题&#xff0c;本文就给大家详细介绍下springboot配置数据库密码加密的方法&#xff0c;下面话不多说了&#xff0c;来一起看看详细的介绍吧 1.导入依赖 <!--数据库密码加密--> <dependency>&…

【算法心得】正确估计dfs时间复杂度;剪枝优化不怕重构

https://leetcode.cn/problems/verbal-arithmetic-puzzle/ 这题看到题&#xff0c;“表达式中使用的不同字符数最大为 10”&#xff0c;就觉得dfs就完事了&#xff0c;最多不过10!&#xff0c;10!才1e6&#xff0c;1e7这样。如果字符再少点&#xff0c;6! 7! 8!的&#xff0c;…

Java之抽象类

Java之抽象类 抽象类概念抽象类如何使用抽象类的特性 作者简介&#xff1a; zoro-1&#xff0c;目前大一&#xff0c;正在学习Java&#xff0c;数据结构等 作者主页&#xff1a;zoro-1的主页 欢迎大家点赞 &#x1f44d; 收藏 ⭐ 加关注哦&#xff01;&#x1f496;&#x1f49…

Pandas+Pyecharts | 成都大运会奖牌数据分析可视化

文章目录 &#x1f3f3;️‍&#x1f308; 1. 导入模块&#x1f3f3;️‍&#x1f308; 2. Pandas数据处理2.1 读取数据2.2 数据信息2.3 数据处理 &#x1f3f3;️‍&#x1f308; 3. Pyecharts数据可视化3.1 每日奖牌数量分布3.2 奖牌榜单TOP20金银铜牌分布3.3 各比赛项目金牌…

微服务参数透传实现

说明&#xff1a;在微服务架构中&#xff0c;用户身份经网关验证后&#xff0c;我们可以将用户信息&#xff0c;如ID加入到请求头上。后面的微服务中&#xff0c;可以设置一个拦截器&#xff0c;拦截请求&#xff0c;获取请求头上的用户ID&#xff0c;加入到ThreadLocal中。 最…

NLP | 论文摘要文本分类

基于论文摘要的文本分类与关键词抽取挑战赛​​​​​​2023 iFLYTEK A.I.开发者大赛-讯飞开放平台 环境需求&#xff1a;Anaconda-JupyterNotebook&#xff0c;或者百度AIStudio 赛题解析&#xff1a; 【文本二分类任务】根据论文摘要等信息理解&#xff0c;将论文划分为0-1两…

css3+js 画出爱心特效

要使用CSS3和JavaScript绘制爱心特效&#xff0c;可以使用CSS3的动画和过渡效果来创建爱心的形状&#xff0c;并使用JavaScript来控制动画的触发和交互。以下是一个简单的示例代码&#xff1a; HTML: <div class"heart"></div> <button onclick&quo…

中路对线发现正在攻防演练中投毒的红队大佬

背景 2023年8月14日晚&#xff0c;墨菲安全实验室发布《首起针对国内金融企业的开源组件投毒攻击事件》NPM投毒事件分析文章&#xff0c;紧接着我们在8月17日监控到一个新的npm投毒组件包 hreport-preview&#xff0c;该投毒组件用来下载木马文件的域名地址竟然是 img.murphys…