Rust学习-生命周期

Rust 最与众不同的功能

之前学习中,有多种可能类型时必须注明类型;同理,引用的生命周期以一些不同方式相关联时,需要使用泛型生命周期参数来注明关系,这样就能确保运行时实际使用的引用有效

避免悬垂引用

{// 声明了没有初始值的变量,这些变量存在于外部作用域// 尝试在给它一个值之前使用这个变量,会出现一个编译错误//  Rust 不允许空值let r;{let x = 5;r = &x;// r 引用的值在尝试使用之前就离开了作用域}println!("r: {}", r);
}

Rust 编译器有一个 借用检查器(borrow checker)
它比较作用域来确保所有的借用都是有效

泛型生命周期

下文编译不通过,返回值需要一个泛型生命周期参数
生命周期标注并不改变任何引用的生命周期的长短
生命周期标注描述多个引用生命周期相互的关系,不影响其生命周期

示例

fn main() {let string1 = String::from("abcd");let string2 = "xyz";// 获取作为引用的字符串 slice,因为不希望 longest 函数获取参数的所有// 期望该函数接受 String 的 slice(参数 string1 的类型)和字符串字面量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}
}// error[E0106]: missing lifetime specifier
// fn longest(x: &str, y: &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`
// help: consider introducing a named lifetime parameter
// 返回值需要一个泛型生命周期参数,Rust 并不知道要返回的引用指向 x/y
// 为了修复这个错误,将增加泛型生命周期参数来定义引用间的关系以便借用检查器可以进行分析

生命周期的添加

// 位于引用的 & 之后,并有一个空格来将引用类型与生命周期标注分隔开
&i32        // 引用
&'a i32     // 带有显式生命周期的引用
&'a mut i32 // 带有显式生命周期的可变引用

单个生命周期标注本身没有多少意义,生命周期标注告诉 Rust 多个引用的泛型生命周期参数如何相互联系
如果函数有一个生命周期 'a 的 i32 的引用的参数 first,有一个生命周期 'a 的 i32 的引用的参数 second,这两生命周期标注意味着引用 first 和 second 必须与这泛型生命周期存在一样久

函数签名中的生命周期

当函数签名中指定了泛型类型参数后就可以接受任何类型
当指定了泛型生命周期后函数能接受任何生命周期的引用

【手动标记生命周期的原因】
当函数引用或被函数之外的代码引用时,让 Rust 自身分析出参数或返回值的生命周期几乎是不可能的。这些生命周期在每次函数被调用时都可能不同。这也就是为什么需要手动标记生命周期

// 像泛型类型参数,泛型生命周期参数需要声明在函数名和参数列表间的尖括号中
// 它告诉 Rust,关于参数中引用和返回值之间的限制是他们都必须拥有相同的生命周期
// longest 函数返回的引用的生命周期与传入该函数的引用的生命周期的较小者一致
// 即当具体引用被传递给 longest 时,被 'a 所替代的具体生命周期是 x 的作用域与 y 的作用域相重叠的那一部分
// 泛型生命周期 'a 的具体生命周期等同于 x 和 y 的生命周期中较小的那一个
// 因为用相同的生命周期参数 'a 标注了返回的引用值,所以返回的引用值就能保证在 x 和 y 中较短的那个生命周期结束之前保持有效
// 
// 通过在函数签名中指定生命周期参数时,并没有改变任何传入值或返回值的生命周期
// 指出任何不满足这个约束条件的值都将被借用检查器拒绝
// 在函数中使用生命周期标注时,标注出现在函数签名中,不存在于函数体中任何代码中
// 参数和返回值都是字符串slicefn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}fn main() {// string1 的作用域较长let string1 = String::from("long string is long");{// string2的 作用域仅限于此let string2 = String::from("xyz");// result的作用域同string2let result = longest(string1.as_str(), string2.as_str());// 正常打印println!("The longest string is {}", result);}// 如下将编译失败// let result;// {//     let string2 = String::from("xyz");//     result = longest(string1.as_str(), string2.as_str());// }// println!("The longest string is {}", result);// error[E0597]: `string2` does not live long enough
}// 打印结果如下:
// The longest string is long string is long

生命周期绑定到函数部分参数

// 如果将 longest 函数的实现修改为总是返回第一个参数而不是最长的字符串 slice
// 就不需要为参数 y 指定一个生命周期
fn longest<'a>(x: &'a str, y: &str) -> &'a str {x
}

返回的引用没有指向任何输入参数

当从函数返回一个引用,返回值的生命周期参数需要与一个参数的生命周期参数相匹配
如果返回的引用没有指向任何参数,唯一的可能就是它指向一个函数内部创建的值,它将会是一个悬垂引用,会在函数结束时离开作用域

fn longest<'a>(x: &str, y: &str) -> &'a str {// 即便为返回值指定了生命周期参数 'a,编译失败// 因为返回值的生命周期与参数完全没有关联// error[E0597]: `result` does not live long enoughlet result = String::from("really long string");// result 在 longest 函数的结尾将离开作用域并被清理// 而尝试从函数返回一个 result 的引用,无法指定生命周期参数来改变悬垂引用result.as_str()
}

解决方案:返回一个有所有权的数据类型而不是一个引用,由函数调用者清理它

总结

生命周期语法用于将函数的多个参数与其返回值的生命周期进行关联。
一旦他们形成了某种关联,Rust 就有了足够的信息来允许内存安全的操作并阻止会产生悬垂指针亦或是违反内存安全的行为

结构体定义中的生命周期标注

// 定义包含引用的结构体,不过需要为结构体定义中的每一个引用添加生命周期标注
// 必须在结构体名称后面的尖括号中声明泛型生命周期参数,在结构体定义中使用生命周期参数
struct ImportantExcerpt<'a> {// 存放一个字符串 slicepart: &'a str,
}fn main() {let novel = String::from("Call me Ishmael. Some years ago...");let first_sentence = novel.split('.').next().expect("Could not find a '.'");// ImportantExcerpt 的实例i不能比其 part 字段中的引用存在的更久let i = ImportantExcerpt { part: first_sentence };
}

生命周期的省略

每一个引用都有一个生命周期
需要为那些使用了引用的函数或结构体指定生命周期
但下文没有指定,但是编译正常,这就是生命周期的省略

【历史原因】
早期版本(pre-1.0)的 Rust ,不能编译
Rust 团队发现特定情况下开发者总是重复地编写一模一样的生命周期标注
这些场景可预测且遵循几个明确的模式。
Rust 团队就把这些模式编码进了 Rust 编译器中,如此借用检查器在这些情况下就能推断出生命周期而不再强制开发者显式的增加标注。
未来只会需要更少的生命周期标注。
如果 Rust 在明确遵守这些规则的前提下变量的生命周期仍然是模棱两可的话,它不会猜测剩余引用的生命周期应该是什么,直接编译报错

【概念】
输入生命周期(input lifetimes) :函数或方法的参数的生命周期
输出生命周期(output lifetimes):返回值的生命周期
生命周期省略规则(lifetime elision rules):被编码进 Rust 引用分析的模式。这并不是需要开发者遵守的规则;这些规则是一系列特定的场景,此时编译器会考虑,如果代码符合这些场景,就无需明确指定生命周期。

【生命周期规则】
(1)输入:每一个是引用的参数都有它自己的生命周期参数
(2)输出:只有一个输入生命周期参数,那么它被赋予所有输出生命周期参数
(3)输出:如果方法有多个输入生命周期参数并且其中一个参数是 &self 或 &mut self,说明是个对象的方法(method), 那么所有输出生命周期参数被赋予 self 的生命周期

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[..]
}

它的隐式解析如下:

// 开始时签名中的引用并没有关联任何生命周期
fn first_word(s: &str) -> &str {// 编译器应用第一条规则:每个引用参数都有其自己的生命周期,假设规则为a:
fn first_word<'a>(s: &'a str) -> &str {// 编译器应用第二条规则:输入参数的生命周期将被赋予输出生命周期参数:
fn first_word<'a>(s: &'a str) -> &'a str {

编译器可以继续它的分析而无须开发者标记这个函数签名中的生命周期

另一个例子

fn longest(x: &str, y: &str) -> &str {
// 应用规则1后就变成如下,但是规则2不适用,报错
fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str {

方法定义中的生命周期标注

为结构体实现方法时,结构体字段的生命周期必须总是在 impl 关键字之后声明,在结构体名称之后被使用

struct ImportantExcerpt<'a> {// 存放一个字符串 slicepart: &'a str,
}impl<'a> ImportantExcerpt<'a> {// 应用第一条生命周期规则,并不必须标注 self 引用的生命周期fn level(&self) -> i32 {3}
}

规则1、3

impl<'a> ImportantExcerpt<'a> {fn announce_and_return_part(&self, announcement: &str) -> &str {println!("Attention please: {}", announcement);self.part}
}
// 应用第一条生命周期省略规则并给予 &self 和 announcement 各自的生命周期
// 其中一个参数是 &self,返回值类型被赋予了 &self 的生命周期

静态生命周期

'static,其生命周期能够存活于整个程序期间
所有的字符串字面量都拥有 'static 生命周期

// 这个字符串的文本被直接储存在程序的二进制文件中而这个文件总是可用的
// 所有的字符串字面量都是 'static
let s: &'static str = "I have a static lifetime.";

将引用指定为 'static 之前,思考一下这个引用是否真的在整个程序的生命周期里都有效
代码中的问题是尝试创建一个悬垂引用或者可用的生命周期不匹配,解决这些问题而不是指定一个 'static 的生命周期

结合泛型类型参数、trait bounds 和生命周期

use std::fmt::Display;// ann 的类型是泛型 T,任何实现了Display trait的类型实例都可以放进去
// 生命周期参数 'a 和泛型类型参数 T 都位于函数名后的同一尖括号列表中
fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a strwhere T: Display
{println!("Announcement! {}", ann);if x.len() > y.len() {x} else {y}
}

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

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

相关文章

Web APIs

文章目录 1.Web APIs 和 JS 基础关联性1.1 JS 的组成 2. API 和 Web API2.1 API2.2 Web API 1.Web APIs 和 JS 基础关联性 1.1 JS 的组成 2. API 和 Web API 2.1 API **API&#xff08;Application Programming Interface,应用程序编程接口&#xff09;**是一些预先定义的函…

观察者模式(下):如何实现一个异步非阻塞的EventBus框架?

上一节课中&#xff0c;我们学习了观察者模式的原理、实现、应用场景&#xff0c;重点介绍了不同应用场景下&#xff0c;几种不同的实现方式&#xff0c;包括&#xff1a;同步阻塞、异步非阻塞、进程内、进程间的实现方式。 同步阻塞是最经典的实现方式&#xff0c;主要是为了…

SSH框架简介篇

文章目录 概述目录结构 strutsSpringHibernate总结 概述 SSH框架&#xff08;Struts Spring Hibernate&#xff09;是一种广泛应用的Java企业级开发框架组合&#xff0c;它将Struts、Spring和Hibernate三个优秀的框架有机地结合在一起&#xff0c;提供了一套完整的解决方案&…

Linux系统编程:文件系统和inode

目录 一. 磁盘的结构和读写数据的方式 1.1 磁盘级文件和内存级文件 1.2 磁盘的物理结构 1.3 访问磁盘数据的方式 二. 磁盘文件系统 2.1 磁盘的分区管理方法 2.2 文件名和inode的关系 三. 结合文件系统对文件创建和删除的相关问题的理解 3.1 文件创建时操作系统进行的工…

Mysql8.0的bin log日志

文章目录 一、 Mysql8.0 的bin log 日志关闭1.1、查看是否已开启 bin log 日志1.2、关闭 bin log 日志1.3、 设置 bin log 日志的时长1.3.1、第一种设置方式&#xff1a;1.3.2、第二种设置方式 一、 Mysql8.0 的bin log 日志关闭 Mysql8.0默认开启 binlog 记录功能&#xff0c…

如何配置Git工具

①安装Git&#xff1a;首先确保你已经在计算机上安装了Git。你可以从Git官方网站&#xff08;https://git-scm.com/&#xff09;下载适合你操作系统的安装程序&#xff0c;并按照提示进行安装。 ② 配置用户信息&#xff1a;在命令行终端中&#xff0c;使用下面的命令来配置你…

51单片机--DS1302时钟

文章目录 DS1302引脚定义和应用电路内部结构框图寄存器的定义时序定义BCD码DS1302时钟代码 DS1302 DS1302是美国DALLAS公司推出的一款实时时钟电路芯片。它具有高性能和低功耗的特点&#xff0c;可以通过SPI三线接口与CPU进行同步通信。DS1302能够提供秒、分、时、日、星期、月…

【SQL应知应会】表分区(一)• MySQL版

欢迎来到爱书不爱输的程序猿的博客, 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 本文收录于SQL应知应会专栏,本专栏主要用于记录对于数据库的一些学习&#xff0c;有基础也有进阶&#xff0c;有MySQL也有Oracle 分区表 • MySQL版 一、分区表1.非分区表2.分区表2…

Autograd:自动求导

Autograd&#xff1a;自动求导 PyTorch中&#xff0c;所有神经网络的核心是 autograd 包。先简单介绍一下这个包&#xff0c;然后训练我们的第一个的神经网络。 autograd 包为张量上的所有操作提供了自动求导机制。它是一个在运行时定义(define-by-run&#xff09;的框架&…

利用集合框架实现-超市会员管理系统

借助集合框架来实现超市会员管理系统&#xff0c;实现以下功能&#xff1a; 1.开卡 2.积分累计 3.查询剩余积分 4.积分兑换 5.修改密码 6.退出 -------------------------------------------------------------------------------------------------- 展示&#x…

【信号去噪和分类】基于小波的隐马尔可夫模型统计信号处理(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

C语言实现扫雷【经典】

前言   本篇文章要实现的是扫雷游戏&#xff0c;其代码实现与上一篇的三子棋游戏类同&#xff0c;都是在棋盘的基础上&#xff0c;与电脑进行对抗&#xff0c;不同的是&#xff0c;扫雷游戏一开始电脑就已经随机布置好了所有“雷”。 请戳 --->三子棋 扫雷游戏 1. 扫雷游…

【新日语】第17課

练习A 语法练习 一、これはnanakoという電子マネーです。 这是叫做nanako的电子钱包。 解析&#xff1a; これ&#xff1a;“这个”&#xff1b;指示代词 ⇒ これ&#xff0f;あれ&#xff0f;それ「第4課ーP33」。 二、これは留学生が読む雑誌です。 这个是留学生读的杂志…

MySQL每日一练——MySQL多表查询进阶挑战

目录 1、首先创建表 t_dept: t_emp: 2、插入数据 t_dept表&#xff1a; t_tmp表: 3、修改表 4、按条件查找 1、首先创建表 t_dept: CREATE TABLE t_dept (id INT(11) NOT NULL AUTO_INCREMENT,deptName VARCHAR(30) DEFAULT NULL,address VARCHAR(40) DEFAULT NULL,P…

Python结巴中文分词笔记

&#x1f4da; jieba库基本介绍 &#x1f310; jieba库概述 Jieba是一个流行的中文分词库&#xff0c;它能够将中文文本切分成词语&#xff0c;并对每个词语进行词性标注。中文分词是自然语言处理的重要步骤之一&#xff0c;它对于文本挖掘、信息检索、情感分析等任务具有重要…

Linux服务器丢包故障的解决思路及引申的TCP/IP协议栈理论

Linux服务器丢包故障的解决思路及引申的TCP/IP协议栈理论 我们使用Linux作为服务器操作系统时&#xff0c;为了达到高并发处理能力&#xff0c;充分利用机器性能&#xff0c;经常会进行一些内核参数的调整优化&#xff0c;但不合理的调整常常也会引起意想不到的其他问题&#x…

Elasticsearch原理剖析

一、 Elasticsearch结构 Elasticsearch集群方案由EsMaster、EsClient和EsNode1、EsNode2、EsNode3、EsNode4、EsNode5、EsNode6、EsNode7、EsNode8、EsNode9进程组成&#xff0c;如下图所示&#xff0c;模块说明如表下所示。 说明如表&#xff1a; 名称说明ClientClient使用H…

Android系统启动流程分析

当按下Android系统的开机电源按键时候&#xff0c;硬件会触发引导芯片&#xff0c;执行预定义的代码&#xff0c;然后加载引导程序(BootLoader)到RAM&#xff0c;Bootloader是Android系统起来前第一个程序&#xff0c;主要用来拉起Android系统程序&#xff0c;Android系统被拉起…

C# Linq 详解四

目录 概述 二十、SelectMany 二十一、Aggregate 二十二、DistinctBy 二十三、Reverse 二十四、SequenceEqual 二十五、Zip 二十六、SkipWhile 二十七、TakeWhile C# Linq 详解一 1.Where 2.Select 3.GroupBy 4.First / FirstOrDefault 5.Last / LastOrDefault C# Li…

Prompt本质解密及Evaluation实战与源码解析(一)

第9章 Prompt本质解密及Evaluation实战与源码解析 9.1 Customer Service案例 本节主要谈提示词(Prompt)内部的工作机制,围绕案例、源码、论文三个维度展开。首先,我们可以看一下代码部分,这是对基于大模型应用程序开发的一个评估(Evaluation),这显然是一个至关重要的内…