Rust 入门 生命周期-next2 (十九)

生命周期消除

实际上,对于编译器来说,每一个引用类型都有一个生命周期,那么为什么我们在使用过程中,很多时候无需标注生命周期?例如:

fn first_word(s: &str) -> &str {let bytes = s.as_bytes();for (i,&item) in bytes.iter().enumerate() {if item == b' ' {return &s[0..i];}}&s[..]
}

该函数的参数和返回值都是引用类型,尽管我们没有显式的为其标柱生命周期, 编译依然可以通过。 其实原因不复杂,编译器为了简化用户的使用,运用来生命周期消除大法。

对于first_word 函数, 它的返回值是一个引用类型,那么该引用只有两种情况:

       1.从参数获取

        2.从函数体內新创建的变量获取

如果是后者,就会出现悬垂引用,最终被编译器拒绝,因此只剩一种情况:返回值的引用是获取自参数。 这就意味着参数和返回值的生命周期是一样的。 道理很简单,我们能看出来,编译器自然也能看出来,因此。就算我们不标注生命周期,也不会产生其一。

实际上,在Rust 1.0 版本之前。这种代码果断不给通过,因为Rust 要求必须显式的为所有引用标注生命周期

fn first_word<'a>(s: &'a str) -> & 'a str {

在些来大量的类似代码后,Rust社区抱怨声四起,包括开发者自己都忍不了了,最终揭锅而起,这才有来我们今日的幸福。

生命周期消除的规则不是一蹴而就,而是伴随着 总结-改善 流程的周而复始,一步一步走到今天,这也意味着,该规则以后可能也会进一步增加,我们需要手动标注生命周期的时候也会越来越少。 

在开始之前有几点需要注意

        1.消除规则不是万能的, 若编译器不能确定某件事是正确时, 会直接判为不正确。那么你还是需要手动标注生命感周期

        2.函数或者方法中,参数的生命周期被成为  输入生命周期,返回值的生命周期 被成为 输出生命周期

三条消除规则

编译器使用三条消除规则来确定哪些场景不需要显式地去标注生命周期,其中第一条规则应用在输入生命周期上,第二,三条应用在输出生命周期上,若编译器发现三条规则都不适用时,就会报错, 提示你需要手动标注生命周期。

        1.每一个引用参数都会获得独自的生命周期

        例如一个引用参数的函数就有一个生命周期标注: fn foo<'a>(x: &'a i32),  两个引用参数的有两个生命周期标注 fn foo<'a,'b>(x: &'a i32, y: &'b i32) 依此类推

        2.若只有一个输入生命周期(函数参数中只有一个引用类型) ,那么该生命周期会被赋给所有的输出生命周期, 也就是所有返回值的生命周期都等于该输入生命周期。

        例如函数 fn foo(x: &i32) -> &i32, x 参数的生命周期会被自动赋给返回值,&i32,  因此该函数等同于 fn foo<'a>(x: &'a i32) -> &'a i32

        3. 若存在多个输入生命周期,且其中一个是&self 或 &mut self ,则 &self 的生命周期被赋给所有的输出生命周期

        拥有 &self  形式的参数,说明该函数是一个 方法, 该规则让方法的使用便利度大幅提升。 

规则其实很好理解,但是,爱是靠的读者肯定要发问来, 例如第三条规则,若一个方法,它的返回值的生命周期就是跟参数 &self 的不一样怎么办? 总不能强迫我返回的值总是和 &self 活得一样久吧? 答案很记得那年: 手动标注生命周期,因为这些规则知识编译器发现你没有标注生命周期时默认去使用的, 当你标注生命周期后, 编译器自然会乖乖听你的话。

让我们假装自己是编译器,然后看下以下的函数该如何引用这些规则:

例子1 

fn first_word(s: &str) -> &str { // 实际项目中的手写代码

首先,我们手写的代码如上所示时,编译器会想引用第一条规则, 为每个参数标注一个生命周期: 

fn first_word<'a>(s: &'a str) -> &str{ // 编译器自动为参数添加生命周期

此时,第二条规则就可以进行应用,因为函数只有一个输入生命周期,因此该生命周期会被赋予所有的输出生命周期: 

fn first_word<'a>(s: &'a str) -> &'a str{ // 编译器自动为返回值添加生命周期

此时,编译器为函数签名中的所有引用都自动添加来具体的生命周期,因此编译通过,且用户无需手动去标注生命周期,只要按照 fn  first_word(s: &str) -> &str { 的形式写代码即可。

例子2

fn longest(x: &str,y: &str) -> &str { // 实际项目中的手写代码

首先,编译器会引用第一条规则,为每个参数都标注生命周期: 

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

但是此时,第二条规则却无法被使用,因为输入生命周期有两个,第三条规则也不符合,因为它是函数,不是方法,因此没有 &self 参数。 在挑用所有规则后,编译器依然无法为返回值标注合适的生命周期。因此,编译器就会报错,提示我们需要手动标注生命周期。

error[E0106]: missing lifetime specifier
--> src/main.rs:1:47
|
1 | fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
|                       -------     -------     ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
note: these named lifetimes are available to use
--> src/main.rs:1:12
|
1 | fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {
|            ^^  ^^
help: consider using one of the available lifetimes here
|
1 | fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &'lifetime str {
|                                                +++++++++

  不得不说,Rust 编译器真的很强大,还贴心的给我们提示来该如何修改,虽然。。。好像。。。 。它的提示貌似不太准确,这里我们希望参数和返回值都是 'a 生命周期。

方法中的生命周期

先来回忆下泛型的语法: 

struct Point<T> {x: T,y: T,
}impl<T> Point<T> {fn x(&self) -> &T {&self.x}}

实际上,为具有生命周期的结构体实现方法时,我们使用的语法跟泛型参数语法很相似: 

struct ImportantExcerpt<'a> {part: &'a str,
}impl<'a> ImportantExcerpt<'a> {fn level(&self) -> i32 {3}
}

其中有几点需要注意的:

         1.impl 中必须使用结构体的完整名称, 包括 <'a> , 因为生命周期标注也是结构体类型的一部分!

        2.方法签名中,往往不需要标注生命周期,得益于生命周期消除的第一和第三规则 

下面的例子展示来第三规则引用的场景:

impl<'a> ImportantExcerpt<'a> {fn announce_and_return_part(&self , announcement: &str) -> &str{println!("Attention please: {}",announcement);self.part}    
}

首先,编译器引用第一规则,给予每个输入参数一个生命周期: 

impl<'a> ImportantExcerpt<'a> {fn announce_and_return_part<'b>(&'a self,announcement: &'b str) -> &str {println!("Attention please:{} ",announcement);self.part}
}

需要注意的是, 编译器不知道 announcement 的生命周期带地多长,因此它无法简单的给予它生命周期 'a , 而是重新声明了一个全新的生命周期 'b . 

接着,编译器应用第三规则额,将 &self 的生命周期赋给返回值 &str :

impl<'a> ImportantExcerpt<'a> {fn announce_and_return_aprt<'b> (&'a self, announcement: &'b str) -> 'a str{println!("Attention please: {}",announcement);self.part}    
}    

最开始的代码,尽管我们没有给方法标注生命周期,但是在第一和第三规则的配合下,编译器依然完美的为我们亮起来绿灯。

在结束这块内容之前,再来做一个有趣的修改, 将方法返回的生命周期改为'b : 

impl<'a> ImportantExcerpt<'a> {fn announte_and_return_part<'b>(&'a self,announcement: & 'b str) -> &'b str {println!("Attention please: {}",announcement);self.part}
}

此时编译器会报错,因为编译器无法知道 'a 和 'b 的关系,&self  生命周期是'a  ,那么 self.part 的生命周期也是  'a , 但是好巧不巧的是,我们手动为返回值 self.part 标注来生命周期 'b, 因此编译器知道 'a  和 'b 的关系。 

有一点很容易推理出来: 由于 &'a self 是被引用的一方,因此引用它的&'b str 必须要活得比它端,否则会出现悬垂引用。 因此说明生命周期 'b 必须要比 'a 小,只要满足来这一点,编译器就不会在报错

impl<'a: 'b,'b> ImportantExcerpt<'a> {fn announce_and_return_part(&'a self,announcement: &'b str) -> &'b str{println!("attention please: {}",announcement);self.part}
}

一个复杂的玩意被甩到来你面前,就问怕不怕?

就关键点稍微解释下: 

        1.‘a: 'b, 是生命周期约束语法, 跟泛型越说非常显式,用于说明'a 必须比'b 活得九

        2.可以把 'a 和 'b 都在同一个地方声明:(如上),或者分开声明 但通过where 'a:'b 约束生命周期关系

impl<'a> ImportantExcerpt<'a> {fn announce_add_return_part<'b>(&'a self,announcement: &'b str) -> &'b str where 'a: 'b,{println!("Attention please: {}",announcement);self.part}
}

总之,实现方法比想象中简单,: 加一个约束,就能暗示编译器,尽管引用吧, 反正我先引用的内容比我活得久

静态生命周期

在Rust中有一个非常特殊的生命周期,那就是 'static ,拥有该生命周期的引用可以和整个程序活得一样久。

在之前我们学过字符串字面量, 提到过它是被硬编码进Rust的二进制文件中,因此这些字符串变量全部具有 'static 的生命周期

let s: &'static str = "我没啥优点,就是活得久,";

这时候,有些聪明的小脑瓜就开始动来, 当生命周期不知道怎么标时,对类型施加一个静态生命周期的约束 T : 'static 是不是很爽? 这样我和编译器再也不用操心它到底活多久来,

嗯,只能说,这个想法是对的,在不少情况下, 'static 约束哦确实可以解决生命周期编译不通过的问题, 但是问题来了,: 本来该引用没有活那么久,但是你非要说它活那么久,玩意引入了潜在的Bug 怎么办?

因此遇到因为生命周期导致的编译不通过问题, 首先想的应该是,: 是否是我们试图创建一个悬垂引用,或者是试图匹配不一致的生命周期,而不是简单粗暴的用 'static 来解决问题。

但是话说回来, 存在即合理,有时候, 'static 确实可以帮助我们解决非常复杂的生命周期问题, 甚至是无法被手动解决的生命周期问题, 那么此时就应该放心大胆的用, 只要你确定:  你的所有引用的生命周期都是正确的。知识编译器太笨不懂罢来。

总结下:

        1.生命周期' static 意味着能和程序活得一样久,例如字符串字面量和特征对象

        2.是在遇到解决不了的生命周期标注问题, 可以尝试 T: 'static ,有时候它会给你奇迹

一个复杂例子: 泛型 ,特征约束

手指已经疲软物理,我好想停止,但是华丽的开场都要有与之匹配的谢幕,那我们就用一个稍微复杂点的例子来结束: 

use std::fmt::Display;fn longest_with_an_announcement<'a,T>(x: &'a str,y: &'a str,ann: T,
) -> &'a str where T:Display,
{println!("Announcement! {}",ann);if x.len() > y.len() {x} else {y}
}

依然是熟悉的配方longest  , 但是多来一段废话:ann ,因为要用格式化{}  来输出 ann , 因此需要实现 Display 特征。

总结

我不知道支撑我一口气写完的勇气是什么, 也许是不做完不算夫斯基,也许是一些读者对本书的期待,不管如何, 这章足足写了17000字,可惜不是写小说, 不然肯定可以获取很多月票;

但是还没完,是的,就算是将近两万字,生命周期的旅程依然没有完结,在本书的进阶部分,我们将介绍一些关于生命周期的高级特性, 这些特性你在其它中文书中目前还看不到的。

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

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

相关文章

Three.js 动画循环学习记录

在上一篇文章中&#xff0c;我们学习了Three.js 坐标系系统与单位理解教程&#xff1a; Three.js 坐标系系统与单位理解教程 接下来我们要学习的是Three.js 的动画循环 一、动画循环基础原理 1. 什么是动画循环&#xff1f; 动画循环是连续更新场景状态并重新渲染的过程&am…

ktg-mes 改造成 Saas 系统

ktg-mes 改造成 Saas 系统 快速检验市场&#xff0c;采用最简单的方案&#xff0c;即添加表字段 截止2025年8月16日上传的ktg-mes搭建存在一些问题&#xff0c;搭建可看文章&#xff1a; 搭建ktg-mes 改造 1. 添加租户表 create table sys_tenant (tenant_id bigint au…

【新手易混】find 命令中 -perm 选项的知识点

find 命令是 Linux/Unix 系统中强大的文件查找工具&#xff0c;广泛用于根据文件名、类型、时间、权限等条件搜索文件。其中&#xff0c;-perm 选项用于按文件权限查找文件&#xff0c;而在 -perm /mode 中出现的斜杠 / 是一种特殊的语法&#xff0c;表示“按位或&#xff08;O…

gdb的load命令和传给opeocd的monitor flash write_image erase命令的区别

问&#xff1a; "monitor flash write_image erase ${workspaceFolder}/obj/ylad_led_blink.elf", 和 "load", "executable" : "${workspaceFolder}/obj/ylad_led_blink.elf", 的区别&#xff1f;答&#xff1a; 你提到的 "monit…

1. Docker的介绍和安装

文章目录1. Docker介绍核心概念核心优势与虚拟机的区别一句话总结2. Docker的安装Windows 10/11 安装 Docker Desktop&#xff08;推荐 WSL2 方式&#xff09;Linux&#xff08;以 Ubuntu / Debian 系为例&#xff09;Docker 是一个开源的容器化平台&#xff0c;它允许开发者将…

fastdds.ignore_local_endpoints 属性

Fast DDS 的 fastdds.ignore_local_endpoints 属性用于控制同一 DomainParticipant 下的本地端点&#xff08;即 DataWriter 和 DataReader&#xff09;是否自动匹配。以下是对该功能的详细解释&#xff0c;并翻译为中文&#xff0c;结合其上下文、实现原理和使用场景&#xff…

华清远见25072班C语言学习day11

重点内容:函数&#xff1a;定义&#xff1a;返回值类型 函数名(参数列表) { //函数体 }函数的参数列表中可以有多个数据返回值&#xff1a;如果函数没有返回值可以写成void 返回值的作用&#xff0c;函数的结果用来返回给主调函数的&#xff0c;如果主调函数处不需要函数的结果…

视觉语言导航(7)——VLN的数据集和评估方法 3.2

这是课上做的笔记&#xff0c;因此很多记得比较急&#xff0c;之后会逐步完善&#xff0c;每节课的逻辑流程写在大纲部分。成功率(SR)导航误差(NE)成功加权路径长度&#xff08;SucceedPLength&#xff09;轨迹长度&#xff08;TL&#xff09;先知成功率&#xff08;OS&#xf…

ElasticSearch不同环境同步索引数据

目的&#xff1a;在生产环境把一个索引的数据同步到测试环境中1、在生产环境导出json数据curl -u "adims_user:xkR%cHwR5I9g" -X GET "http://172.18.251.132:9200/unify_info_mb_sp_aggregatetb_0004/_search?scroll1m" -H Content-Type: applicatio…

咨询进阶——解读咨询顾问技能模型

适应人群为咨询行业从业者、咨询团队管理者、想提升咨询技能的职场人士及咨询公司培训人员。主要内容围绕咨询顾问技能模型展开,核心包括五大核心能力(解决问题能力,涵盖洞察力、分析技巧、问题构建等,从识别问题实质到构建新分析方法分层次阐述;管理能力,涉及管理他人与…

2025年- H98-Lc206--51.N皇后(回溯)--Java版

1.题目描述2.思路 二维数组集合 (1&#xff09;N皇后规则 1&#xff09;不能同行&#xff08;同一行不能出现2个皇后&#xff09; 2&#xff09;不能同列&#xff08;同一列不能出现2个皇后&#xff09; 3&#xff09;不能说45度或135度&#xff08;斜对角线不能出现2个皇后&am…

5G + AI + 云:电信技术重塑游戏生态与未来体验

在数字娱乐蓬勃发展的今天&#xff0c;游戏产业已然成为科技创新的前沿阵地。电信网络也经历了一场深刻的蜕变&#xff0c;从最初仅仅是 “内容传输管道”&#xff0c;摇身一变成为与游戏深度绑定的技术共生体。5G 不断刷新着体验的边界&#xff0c;AI 彻底颠覆传统的创作模式&…

【React Hooks】封装的艺术:如何编写高质量的 React 自-定义 Hooks

【React Hooks】封装的艺术&#xff1a;如何编写高质量的 React 自-定义 Hooks 所属专栏&#xff1a; 《前端小技巧集合&#xff1a;让你的代码更优雅高效》 上一篇&#xff1a; 【React State】告别 useState 滥用&#xff1a;何时应该选择 useReducer 作者&#xff1a; 码力…

华为GaussDB的前世今生:国产数据库崛起之路

在数据库领域&#xff0c;华为GaussDB已成为一颗耀眼的明星&#xff0c;为企业核心业务数字化转型提供坚实的数据底座。但这并非一蹴而就&#xff0c;其背后是长达二十余年的技术沉淀、战略投入与持续创新。本文将深入探寻华为GaussDB的历史沿革与核心技术细节&#xff0c;展现…

数据结构初阶(16)排序算法——归并排序

2.4 归并排序 归并排序&#xff08;Merge Sort&#xff09;是基于分治思想的经典排序算法。核心逻辑&#xff1a; 分而治之——把复杂排序问题拆分成简单子问题解决&#xff0c;再合并子问题的结果。联系链表的合并&#xff1a;两个有序链表l1、l2创建新链表l3&#xff08;带头…

MATLAB实现匈牙利算法求解二分图最大匹配

MATLAB实现匈牙利算法求解二分图最大匹配 匈牙利算法&#xff08;也称为Kuhn-Munkres算法&#xff09;是解决二分图最大匹配问题的经典算法。 代码 function [matching, max_match] hungarian_algorithm(adjMatrix)% HUNGARIAN_ALGORITHM 实现匈牙利算法求解二分图最大匹配% 输…

自定义table

更好<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"utf-8"><title>数据表格</title><style>* {margin: 0;padding: 0;box-sizing: border-box;font-size: 14px;}html,body {width: 100%;height: 100%…

面向R语言用户的Highcharts

如果您喜欢使用 R 进行数据科学创建交互式数据可视化&#xff0c;那么请你收藏。今天&#xff0c;我们将使用折线图、柱状图和散点图来可视化资产回报。对于我们的数据&#xff0c;我们将使用以下 5 只 ETF 的 5 年月回报率。 SPY (S&P500 fund)EFA (a non-US equities fun…

【测试工具】OnDo SIP Server--轻松搭建一个语音通话服务器

前言 Ondo SIP Server 是一款基于 SIP(Session Initiation Protocol)协议的服务器软件&#xff0c;主要用于实现 VoIP(Voice over IP)通信&#xff0c;支持语音通话、视频会议等多媒体会话管理&#xff0c;非常适合学习和测试VoIP的基本功能。本文介绍Ondo SIP Server的安装、…

疯狂星期四文案网第42天运营日记

网站运营第42天&#xff0c;点击观站&#xff1a; 疯狂星期四 crazy-thursday.com 全网最全的疯狂星期四文案网站 运营报告 今日访问量 今日搜索引擎收录情况 网站优化点 优化一些发现的seo错误 增加颜文字栏目 增加了一些tag