【第七课】Rust所有权系统(三)

ops/2024/11/22 13:16:05/

目录

前言

生命周期

生命周期的规则

总结


前言

上一节课讲述了所有权系统中的引用与借用,引用可以让我们对一块内存上的数据有读权限或者读写权限,但是不会有该内存的释放权限,因为释放权限只会属于所有者,这是所有权原则中的一块内存在某一个时刻只能有一个所有者规则约束的。这节课在引用的基础上继续讲解下一个重要概念:生命周期。我们想一想那个毕业生离开班级的例子,假设A同学是字典的所有者,B同学,C同学都借用该字典,拥有该字典的引用,那么如果同学A还没有离开班级时,同学B和同学C都可以正常访问该字典,假设同学A离开了教室,将字典带走了,那么同学B和同学C对于该字典的引用其实是很危险的,因为所有者离开作用域后,内存上的数据被清除,引用就会指向一个不存在的地方。那么在使用引用时,我们需要保证,引用指向的地方必须一直有效,最起码,引用离开作用域之前,引用指向的所有者也不能离开作用域,我们将这种存活时间称之为生命周期。它的作用是确保片在程序运行时,引用都是有效的。Rust编译器在编译代码时会帮助我们检查引用的生命周期的有效性,如果检查不通过,编译会报错,聊rust聊了这么多,其实可以很明显的感觉到rust在编译期做了很多事情,这也是很多人诟病的一个地方,觉得rust的编译太慢了,但是反过来说,编译耗时,运行时快且安全,收益其实是非常高的。

生命周期

生命周期在代码中的表现形式就是在引用前面增加一个‘a,比如有一个引用&p,那么加上生命周期后就是&‘a p,表示不可变引用p的生命周期是a。我们来看一下生命周期的例子,毕竟到现在为止,我们还没有看过生命周期。

fn main() {let r;{let i = 1;r = &i;}println!("r = {}", *r);
}

查看上面的代码,我们定义了变量r,在内部花括号中,定义了变量i,i绑定的值是1,将r等于i的不可变引用,当i走出内部花括号后,i因为是1那块内存的所有者,所以i会释放内存,在最后一行,我们在println!中通过r取值,这个操作是一个危险的操作,因为内存已经释放了,问题出在哪里呢?问题出在i的生命周期只在内部的花括号中,但是r的生命周期是整个main函数,r的生命周期大于i的生命周期,但是r是i的不可变引用,r引用了一个生命周期比自己小的生命周期的所有者。这就是生命周期的作用。实际上,上面这段代码在cargo build后会报错,这样危险的代码是不会过检查的,报错如下图,报错也非常明显,提示出i活得时间不够久,i死了之后,依然有人在读取i。

生命周期的规则

rust的编译器会帮我们推导生命周期和检查生命周期的有效性,但能力是有限的,我们看一个rust无法帮我们推导生命周期的例子。下面的代码是很典型,也是很常见的一个例子,下面的代码在编译后会报错,具体报错如下图

fn main() {}fn longest(x: &str, y: &str) -> &str {if x.len() > y.len() {x} else {y}
}

我们来还原一下rust编译器推导生命周期的过程,先提一下生命周期的三个规则:

1.每一个引用参数都有自己的生命周期,我们以上面的函数为例,longest函数有2个参数x和y,所以x和也都有自己的生命周期,就是了longest(x: &'a str, y: &'b str) 

2.如果输入只有一个生命周期参数,那么输出的生命周期等于这个输入的生命周期参数,假设一个函数签名是my_method(x: &str) -> &str,根据第一条规则my_method(x: &‘a str) -> &str;根据第二套规则,my_method(x: &‘a str) -> &’a str。此时编译器只要保证输入的生命周期和返回值的生命周期保持一致就好了,如果不保持一致就会编译报错。

我们结合规则1和规则2来看个例子。我们定义了一个函数,我们按照规则1和规则2来加上生命周期,并且查看是否可以通过检查。可以看到对于函数my_method,输入引用的生命周期和返回引用的生命周期需要一样大,都是'a,那么实际上可以满足么,我们先看x的生命周期,是main函数的从x定义开始,到main函数结束,而返回引用&t的生命周期在函数结束就结束了,很明显&t的生命周期无法满足'a,所以下面这个代码编译会报错。

fn main() {let x = String::from("hi");my_method(&x);
}fn my_method<'a>(x: &'a str) -> &'a str {let t = String::from("hello");&t
}

趁热打铁,我们再分析一下longest函数的生命周期。根据规则1,输入参数x和y分别有自己的生命周期,假设为'a 和 'b,规则2不满足,所以输出引用没有生命周期,rust编译器无法做生命周期有效性的检查,于是会报错。我们补齐一下对于该函数,我们希望保证的生命周期的有效性

fn main() {}fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}

我们来看一段无法通过生命周期检查的代码,如下图,这段代码无法通过的报错截图也贴在了下面,明显的,在函数定义上,希望参数x和参数y的生命周期保持一致,但是在main函数中str1 和 str2的生命周期并不一致,所以报错了,解决也很简单,就是将main中内部{}的代码放在外面,这样子生命周期一致,rust编译器检查通过,程序安全。

贴一下可以通过rust编译器的代码

fn main() {let str1 = String::from("hello");let x = &str1;let y;let str2 = String::from("hi");y = &str2;longest(x, y);
}fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {if x.len() > y.len() {x} else {y}
}

关于生命周期的3大规则,我们介绍了2个,这两个在我们分析编译器报错时,可以解决大部分问题了,第三个规则和面向对象有关,规则为,如果是对象的方法,方法的返回参数的生命周期都等于self的生命周期,这条规则我们放到介绍面向对象时再详细展开。

总结

 这是关于rust所有权系统的最后一课,在引用的基础上,讲述了生命周期是如何保证引用的有效性的,关于rust的所有权系统,是非常精彩的设计思路的迭代与博弈,一开始针对内存设计了所有权,避免了额外的线程去做垃圾回收的操作,比如jvm类的语言,使得rust的内存消耗非常低,也正是因为所有权的转移会导致读写数据的不方便,设计了引用来提供读写数据,因为引用只能读写数据,不能清理内存上的数据,所以引入了生命周期的概念保证了使用引用的安全。环环相扣,构建了rust的整个所有权系统,除了这些好处,有坏处么?当然有,最明显的,对程序员的要求提高了,即使编译器的提示很详细,但是对于初学者来说,还是很不友好,随便写点代码就要调好久,体感很差。


http://www.ppmy.cn/ops/135792.html

相关文章

如何查找 Kafka消息队列中主题Topic的消费者?

这是一篇关于如何查找特定 Kafka 主题&#xff08;如 filter_id&#xff09;消费者的教程&#xff0c;涵盖了使用 Kafka 命令行工具 kafka-consumer-groups.sh 的详细步骤。 如何查找 Kafka 主题的消费者&#xff1f; Kafka 中&#xff0c;消费者组和主题之间的关系是关键的。…

iPhone 17 Air看点汇总:薄至6mm 刷新苹果轻薄纪录

我们姑且将这款iPhone 17序列的超薄SKU称为“iPhone 17 Air”&#xff0c;Jeff Pu在报告中提到&#xff0c;我同意最近关于 iPhone 17超薄机型采用6 毫米厚度超薄设计的传言。 如果这一测量结果被证明是准确的&#xff0c;那么将有几个值得注意的方面。 首先&#xff0c;iPhone…

css水平居中+垂直居中

display:“flex”,position: “absolute”,top:“50%”,left:“50%”,transform: ‘translate(-50%, -50%)’

Notepad++--在开头快速添加行号

原文网址&#xff1a;Notepad--在开头快速添加行号_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Notepad怎样在开头快速添加行号。 需求 原文件 想要的效果 方法 1.添加点号 Alt鼠标左键&#xff0c;从首行选中首列下拉&#xff0c;选中需要添加序号的所有行的首列&#xff…

安全、便捷、效率高,明达边缘计算网关助力制药装备企业远程调机

随着药厂对设备运维需求的增长&#xff0c;制药装备企业需要在提高运维效率的同时&#xff0c;降低人工及差旅成本。制药装备因其数据具有高度的保密性&#xff0c;要求运维工程师提供安全可靠的远程调试方式。本案例介绍了明达技术MBox20系列5口WIFI通用网关在制药装备上的应用…

云原生:构建未来应用的基石

目录 引言 什么是云原生&#xff1f; 云原生的关键组件 1. 容器 实际步骤&#xff1a; 2. 微服务 实际步骤&#xff1a; 3. CI/CD 实际步骤&#xff1a; 4. 不变基础设施 实际步骤&#xff1a; 5. 声明式API 实际步骤&#xff1a; 云原生的优势 1. 快速迭代 实际…

「Mac玩转仓颉内测版19」PTA刷题篇10 - L1-010 比较大小

本篇将继续讲解PTA平台上的题目 L1-010 比较大小&#xff0c;通过对三个整数的排序&#xff0c;进一步提升Cangjie编程语言的数组操作与逻辑处理能力。 关键词 PTA刷题数字排序条件判断Cangjie语言 一、L1-010 比较大小 题目描述&#xff1a;给定3个整数&#xff0c;要求将它…

拼音。。。。。。。。。。

拼音。文心一言、文心大模型3.5&#xff0c;竟然说错了&#xff0c;如下图。所以&#xff0c;以后都不想在文心一言搜拼音了。。以后搜拼音&#xff0c;还是在百度一下直接搜&#xff0c;搜到的顶头第一条也是AI智能回答&#xff0c;可能比文心一言更加好更加准 正确的说法是&…