Rust-借用和生命周期

生命周期

一个变量的生命周期就是它从创建到销毁的整个过程。其实我们在前面已经注意到了这样的现象:

在这里插入图片描述
然而,如果一个变量永远只能有唯一一个入口可以访问的话,那就太难使用了。因此,所有权还可以借用。

借用

变量对其管理的内存拥有所有权。这个所有权不仅可以被转移(move),还可以被借用(borrow)。

借用指针的语法使用&符号或者&mut符号表示。前者表示只读借用,后者表示可读写借用。借用指针(borrow pointer)也可以称作“引用”(reference)。借用指针与普通指针的内部数据是一模一样的,唯一的区别是语义层面上的。它的作用是告诉编译器,它对指向的这块内存区域没有所有权。

在这里插入图片描述
这里会出现编译错误,信息为“cannot borrow immutable borrowed content’*v’as mutable”。原因在于Vec::push函数。它的作用是对动态数组添加元素,它的签名是

pub fn push(&mut self,value:T)

它要求self参数是一个&mut Self类型。而我们给foo传递的参数是&Vec类型,因此会报错。修复方式如下:

在这里插入图片描述
对于&mut型指针,请大家注意不要混淆它与变量绑定之间的语法。

如果mut修饰的是变量名,那么它代表这个变量可以被重新绑定;如果mut修饰的是“借用指针&”,那么它代表的是被指向的对象可以被修改。示例如下:
在这里插入图片描述

借用规则

关于借用指针,有以下几个规则:

  • 借用指针不能比它指向的变量存在的时间更长。
  • &mut型借用只能指向本身具有mut修饰的变量,对于只读变量,不可以有&mut型借用。
  • &mut型借用指针存在的时候,被借用的变量本身会处于“冻结”状态。
  • 如果只有&型借用指针,那么能同时存在多个;如果存在&mut型借用指针,那么只能存在一个;如果同时有其他的&或者&mut型借用指针存在,那么会出现编译错误。

借用指针只能临时地拥有对这个变量读或写的权限,没有义务管理这个变量的生命周期。因此,借用指针的生命周期绝对不能大于它所引用的原来变量的生命周期,否则就是悬空指针,会导致内存不安全。示例如下:

在这里插入图片描述
在这里给大家提个醒:一般情况下,函数参数使用引用传递的时候,不仅在函数声明这里要写上类型参数,在函数调用这里也要显式地使用引用运算符。

但是,有一个例外,那就是当参数为self &self &mut self等时,若使用小数点语法调用成员方法,在函数调用这里不能显式写出借用运算符。以常见的String类型来举例:

在这里插入图片描述
在这个示例中,所有的函数调用都是同样的语法,比如x.len()、x.push(‘!’)、x.into_bytes()等,但它们背后对self参数的传递类型完全不同,因此也就出现了不同的语义。

这是需要提醒大家注意的地方。当然,如果我们使用统一的完整函数调用语法,那么所有的参数传递类型在调用端都是显式写出来的。

任何借用指针的存在,都会导致原来的变量被“冻结”(Frozen)。示例如下:

在这里插入图片描述
编译结果为:

error:cannot assign to `x^because it is borrowed

因为p的存在,此时对x的改变被认为是非法的。

生命周期标记

对一个函数内部的生命周期进行分析,Rust编译器可以很好地解决。但是,当生命周期跨函数的时候,就需要一种特殊的生命周期标记符号了。

函数的生命周期标记

在这里插入图片描述
生命周期符号使用单引号开头,后面跟一个合法的名字。生命周期标记和泛型类型参数是一样的,都需要先声明后使用。在上面这段代码中,尖括号里面的’a是声明一个生命周期参数,它在后面的参数和返回值中被使用。

前面提到的借用指针类型都有一个生命周期泛型参数,它们的完整写法应该是&'a T &'a mut T,只不过在做局部变量的时候,生命周期参数是可以省略的。

生命周期之间有重要的包含关系。如果生命周期’a比’b更长或相等,则记为’a : 'b,意思是’a至少不会比’b短,英语读做“lifetime a outlives lifetime b”。对于借用指针类型来说,如果&'a是合法的,那么’b作为’a的一部分,&'b也一定是合法的。

另外,'static是一个特殊的生命周期,它代表的是这个程序从开始到结束的整个阶段,所以它比其他任何生命周期都长。这意味着,任意一个生命周期’a都满足’static : 'a。

在上面这个例子中,如果我们把变量t的真实生命周期记为’t,那么这个生命周期’t实际上是变量t从“出生”到“死亡”的区间。在函数被调用的时候,它传人的实际参数是&t,它是指向t的引用。那么可以说,在调用的时候,这个泛型参数’a被实例化为了’t。根据函数签名,基于返回类型的生命周期与参数是一致的,可以推理出test函数的返回类型是&'t i32。

如果我们把x的生命周期记为’x。这条let x =text(&t);语句实际上是把&'t i32类型的变量赋值给&'x i32类型的变量。这个赋值是否合理呢?它应该是合理的。因为这两个生命周期的关系是’t: 'x。test返回的那个指针在’t这个生命周期范围内都是合法的,在一个被’t包围的更小范围的生命周期内,它当然也是合法的。所以,上面这个例子可以编译通过。

接下来,我们把上面这个例子稍作修改,让test函数有两个生命周期参数,其中一个给函数参数使用,另外一个给返回值使用:

在这里插入图片描述
编译时果然出了问题,在&arg.member这一行,报了生命周期错误。这是为什么呢?因为这一行代码是把&'a i32类型赋值给&'b i32类型。

'a和’b有什么关系?答案是什么关系都没有。所以编译器觉得这个赋值是错误的。

怎么修复呢?指定’a:'b就可以了。'a比’b“活”得长,自然,&'a i32类型赋值给&'b i32类型是没问题的。

验证如下:

在这里插入图片描述
经过这样的改写后,我们可以认为,在test函数被调用的时候,生命周期参数’a和’b被分别实例化为了’t和’x。

它们刚好满足了where条件中的’t:'x约束。而&arg.member这条表达式的类型是&'t i32,返回值要求的是&'x i32类型,可见这也是合法的。

所以test函数的生命周期检查可以通过。

上述示例是读者比较难理解的地方。以下两种写法都是可行的:

在这里插入图片描述
这里的关键是,Rust的引用类型是支持“协变”的。

在编译器眼里,生命周期就是一个区间,生命周期参数就是一个普通的泛型参数,它可以被特化为某个具体的生命周期。

我们再看一个例子。它有两个引用参数,共享同一个生命周期标记:

在这里插入图片描述
上述示例中,select这个函数引入了一个生命周期标记,两个参数以及返回值都是用的这个生命周期标记。同时我们注意到,在调用的时候,传递的实参其实是具备不同的生命周期的。

x的生命周期明显大于y的生命周期,&x可存活的范围要大于&y可存活的范围,我们把它们的实际生命周期分别记录为’x和’y。

select函数的形式参数要求的是同样的生命周期,而实际参数是两个不同生命周期的引用,这个类型之所以可以匹配成功,就是因为生命周期的协变特性。

编译器可以把&x和&y的生命周期都缩小到某个生命周期’a以内,且满足’x:‘a,'y:‘a。返回的selected变量具备’a生命周期,也并没有超过’x和’y的范围。所以,最终的生命周期检查可以通过。

类型的生命周期标记

如果自定义类型中有成员包含生命周期参数,那么这个自定义类型也必须有生命周期参数。示例如下:

在这里插入图片描述
在使用impl的时候,也需要先声明再使用:
在这里插入图片描述
impl后面的那个’t是用于声明生命周期参数的,后面的Test<'t>是在类型中使用这个参数。

如果有必要的话,方法中还能继续引入新的泛型参数。

如果在泛型约束中有where T:'a之类的条件,其意思是,类型T的所有生命周期参数必须大于等于’a。

要特别说明的是,若是有where T:'static的约束,意思则是,类型T里面不包含任何指向短生命周期的借用指针,意思是要么完全不包含任何借用,要么可以有指向’static的借用指针。

省略生命周期标记

在某些情况下,Rust允许我们在写函数的时候省略掉显式生命周期标记。

在这种时候,编译器会通过一定的固定规则为参数和返回值指定合适的生命周期,从而省略一些显而易见的生命周期标记。比如我们可以写这样的代码:

在这里插入图片描述
实际上,它等同于下面这样的代码,只是把显式的生命周期标记省略掉了而已:

在这里插入图片描述

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

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

相关文章

从零开始:生产环境如何部署 Bytebase

Bytebase 是面向研发和 DBA 的数据库 DevOps 和 CI/CD 协同平台。目前 Bytebase 在全球类似开源项目中 GitHub Star 数排名第一且增长最快。 Bytebase 的架构 Bytebase 是一个单体架构 (monolith)&#xff0c;前端是 Vue3 TypeScript&#xff0c;后端是 Go。前端利用 Go 1.6 …

MySQL面试题 | 09.精选MySQL面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

linux下485通信调试记录

1、使用linux下使用串口调试助手 linux下可以安装并使用下述串口调试工具进行串口测试&#xff1a; 1.1、cutecom cutecom是linux下常用的图形化串口调试软件&#xff0c;通过以下命令安装并打开&#xff1a; sudo apt-get install cutecom sudo cutecom显示如下&#xff1…

【IPC通信--共享内存】

进程间通信目的 数据传输&#xff1a;一个进程需要将它的数据发送给另一个进程 资源共享&#xff1a;多个进程之间共享同样的资源。 通知事件&#xff1a;一个进程需要向另一个或一组进程发送消息&#xff0c;通知它&#xff08;它们&#xff09;发生了某种事件&#xff08;如…

【CSS】解决height = line-height 文字不垂直居中(偏上、偏下)的问题

解决办法1&#xff1a; 查看 font-family 属性&#xff0c;确认是否是因为字体而导致的不垂直居中问题。 其他小知识&#xff1a; 基线就是小写x字母的下边缘(线) 就是我们常说的 基线。line-height 属性设置的行高也就是定义的两行文字基线之间的距离! 参考文章&#xff1a;…

网络共享服务

存储类型&#xff1a;直连式&#xff08;DAS&#xff09;:距离最近&#xff0c;存储设备且直接连接到服务器上 存储区域网络&#xff08;SAN&#xff09;&#xff1a;适用于大型应用或数据库系统&#xff0c;可以使用文件的空间&#xff0c; 以及管理空间…

canvas绘制图片的三种方法(图文示例)

查看专栏目录 canvas示例教程100专栏&#xff0c;提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重…

Apache StringUtils:Java字符串处理工具类

简介 在我们的代码中经常需要对字符串判空&#xff0c;截取字符串、转换大小写、分隔字符串、比较字符串、去掉多余空格、拼接字符串、使用正则表达式等等。如果只用 String 类提供的那些方法&#xff0c;我们需要手写大量的额外代码&#xff0c;不然容易出现各种异常。现在有…

任务13:使用MapReduce对天气数据进行ETL(获取各基站ID)

任务描述 知识点&#xff1a; 天气数据进行ETL 重 点&#xff1a; 掌握MapReduce程序的运行流程熟练编写MapReduce程序使用MapReduce进行ETL 内 容&#xff1a; 编写MapReduce程序编写Shell脚本&#xff0c;获取MapReduce程序的inputPath将生成的inputPath文件传入到Wi…

AWS边缘媒体安全交付方案

企业如何在AWS上的边缘站点&#xff0c;安全的将优质视频内容交付给用户&#xff0c;并且禁止哪些未经过授权的访问&#xff1f;九河云将基于AWS平台提供边缘媒体安全交付解决方案 解决方案详情 在通过 Amazon CloudFront 交付时&#xff0c;免受未经授权的访问。基于添加到交…

单页面vite打包学习

前端工程化本人真的很发怵&#xff0c;一直也没有专心去突破一下&#xff0c;都是能用就用&#xff0c;所以今天小小学习一下打包&#xff0c;先从单页面应用的vite打包开始。本文主要是一些我的大白话和有限的经验&#xff0c;如有问题望指正。 一、问题 网页要从服务器请求…

读书笔记——《未来简史》

前言 《未来简史》是以色列历史学家尤瓦尔赫拉利的人类简史三部曲之一。三部分别为《人类简史》《未来简史》《今日简史》。其中最为著名的当然是《人类简史》&#xff0c;非常宏大的一本关于人类文明历史的书籍&#xff0c;绝对可以刷新历史观&#xff0c;《人类简史》这本书…

磁盘raid1降级后,mdxxx rota发生变化

背景 虚拟机系统盘vda后端使用宿主机ssd盘lvm组raid1,虚拟机内部查看vda磁盘类型(rota=1):机械硬盘,vda后端raid1降级导致磁盘类型降级:rota 0---->1,vda磁盘类型显示不正确,应该是ssd类型(rota=0); 分析 1.基础 1.1 linux磁盘类型 Rota表示磁盘类型:(1)0,表…

css3过渡与动画

css3过渡与动画 前言过渡过渡的基本使用 transition兼容性transition属性基本使用哪些属性可以参与过渡all过渡的四个小属性 过渡的缓动效果常用缓动参数贝塞尔曲线 过渡效果实战 动画动画的定义和调用动画的执行次数 动画效果实战 案例&#xff1a;发光的灯泡案例&#xff1a;…

前端框架前置学习Node.js(2)npm使用,Node.js总结

npm - 软件包管理器 定义 npm是Node.js标准的软件包管理器 npm仓库中包含大量软件包,使其成为世界上最大的单一语言代码仓,并且可以确定几乎可用于一切的软件包 最初是为了下载和管理Node.js包依赖的方式,但其现在已成为前端JavaScript中使用的工具 使用: 1.初始化清单文…

编译 FastDFS 时报错 fatal error: sf/sf_global.h: No such file or directory 解决办法

编译 FastDFS 时&#xff0c;报错如下 gcc -Wall -D_FILE_OFFSET_BITS64 -D_GNU_SOURCE -g -O1 -DDEBUG_FLAG -c -o ../common/fdfs_global.o ../common/fdfs_global.c -I../common -I/usr/local/include In file included from ../common/fdfs_global.c:21:0: ../common/fdf…

力扣每日一题--2088. 统计农场中肥沃金字塔的数目

看到这道题有些人很容易放弃&#xff0c;其实这道题不是很难&#xff0c;主要是题目长&#xff0c;读的容易让人放弃&#xff0c;但是 只要抓住一些性质就可以解决该问题。 本题中的定义放到图像里其实就是个金字塔&#xff0c;下层的那部分比上一层的那部分&#xff0c;长度加…

【PID精讲 14 】积分分离PID和抗积分饱和PID

文章目录 一、积分分离PID1.1 积分分离PID算法基本思想1.2 积分分离PID算法实现步骤1.3 积分分离PID算法1.4 积分分离PID算法实现1.5 积分分离PID算法仿真实例1.6 积分分离PID算法的优缺点 二、抗积分饱和PID2.1 积分饱和现象2.2 抗积分饱和算法2.3 抗积分饱和算法实现2.4 抗积…

排序算法8----归并排序(非递归)(C)

1、介绍 归并排序既可以是内排序&#xff08;在内存上的数据排序&#xff09;&#xff0c;也可以是外排序&#xff08;磁盘上&#xff09;&#xff08;硬盘&#xff09;&#xff08;在文件中的数据排序&#xff09;。 其他排序一般都是内排序。 区别于快速排序的非递归&#xf…

【React源码 - Diff算法】

介绍 在React学习中&#xff0c;Diff算法(协调算法)&#xff0c;想必我们并不陌生&#xff0c;简单来说就是一个对比新老节点寻找差异&#xff0c;然后找出最小的一个变化集&#xff0c;最后对这个最小变化集进行最小的DOM操作&#xff0c;本文将从源码来分析在React(17.0.2)中…