Rust-借用检查

Rust语言的核心特点是:在没有放弃对内存的直接控制力的情况下,实现了内存安全。

所谓对内存的直接控制能力,前文已经有所展示:可以自行决定内存布局,包括在栈上分配内存,还是在堆上分配内存;支持指针类型;可以对一个变量实施取地址操作;有确定性的内存释放;等等。

另一方面,从安全性的角度来说,我们可以看到,Rust有所有权概念、借用指针、生命周期分析等这些内容。

随便写个小程序都编译不通过,学习曲线非常陡峭。那么,Rust设计者究竟是如何考虑的这个问题,为什么要设计这样复杂的规则?Rust语言的这一系列安全规则,背后的指导思想究竟是什么呢?

总的来说,Rust的设计者们在一系列的“内存不安全”的问题中观察到了这样的一个结论:

在这里插入图片描述
首先我们介绍一下这两个概念Alias和Mutation。

  1. Alias的意思是“别名”。如果一个变量可以通过多种Path来访问,那它们就可以互相看作alias。Alias意味着“共享”,我们可以通过多个入口访问同一块内存。
  2. Mutation的意思是“改变”。如果我们通过某个变量修改了一块内存,就是发生了mutation。Mutation意味着拥有“修改”权限,我们可以写入数据。

Rust保证内存安全的一个重要原则就是,如果能保证alias和mutation不同时出现,那么代码就一定是安全的。

编译错误示例

它们主要关心的是共享和可变之间的关系。“共享不可变,可变不共享”是所有这些编译错误遵循的同样的法则。

下面我们通过几个简单的示例来直观地感受一下这个规则究竟是什么意思。

示例一

在这里插入图片描述
以上这段代码是可以编译通过的。其中变量绑定i、p1、p2指向的是同一个变量,我们可以通过不同的Path访问同一块内存p,*p1,*p2,所以它们存在“共享”。而且它们都只有只读的权限,所以它们存在“共享”,不存在“可变”。因此它一定是安全的。

示例二
我们让变量绑定i是可变的,然后在存在p1的情况下,通过i修改变量的值:

在这里插入图片描述
编译,出现了错误,错误信息为:

error:cannot assign to 'i’because it is borrowed [E0506]

这个错误可以这样理解:在存在只读借用的情况下,变量绑定i和p1已经互为alias,它们之间存在“共享”,因此必须避免“可变”。这段代码违反了“共享不可变”的原则。

示例三
如果我们把上例中的借用改为可变借用的话,其实是可以通过它修改原来变量的值的。以下代码可以编译通过:

在这里插入图片描述
那我们是不是说,它违反了“共享不可变”的原则呢?其实不是。因为这段代码中不存在“共享”。在可变借用存在的时候,编译器认为原来的变量绑定i已经被冻结(frozen),不可通过i读写变量。此时有且仅有p1这一个入口可以读写变量。证明如下:

在这里插入图片描述
在pl存在的情况下,不可通过i写变量。如果这种情况可以被允许,那就会出现多个入口可以同时访问同一块内存,且都具有写权限,这就违反了Rust的“共享不可变,可变不共享”的原则。错误信息为:

error:cannot assign to `i’because it is borrowed [E0506]

示例四
同时创建两个可变借用的情况:

在这里插入图片描述
编译错误信息为:

error:cannot borrow 'i’as mutable more than once at a time [E0499]

因为p1、p2都是可变借用,它们都指向了同一个变量,而且都有修改权限,这是Rust不允许的情况,因此这段代码无法编译通过。

正因如此,&mut型借用也经常被称为“独占指针”&型借用也经常被称为“共享指针”。

内存不安全示例:修改枚举

Rust设计的这个原则,究竟有没有必要呢?它又是如何在实际代码中起到“内存安全”检查作用的呢?
第一个示例,我们用enum来说明。假如我们有一个枚举类型:

在这里插入图片描述
它有两个元素,分别可以携带String类型的信息以及i64类型的信息。

假如我们有一个引用指向了它的内部数据,同时再修改这个变量,大家猜想会发生什么情况?这样做可能会出现内存安全问题,因为我们有机会用一个String类型的指针指向i64类型的数据,或者用一个i64类型的指针指向String类型的数据。完整示例如下:

在这里插入图片描述
在这段代码中,我们用if let语法创建了一个指向内部string的指针,然后在此指针的生命周期内,再把x内部数据变成i64类型。这是典型的内存不安全的场景。

幸运的是,这段代码编译不通过,错误信息为:

error:cannot assign to `x’because it is borrowed [E0506]

这个例子给了我们一个直观的感受,为什么Rust需要“可变性和共享性不能同时存在”的规则?保证当前只有一个访问入口,这是保证安全的可靠做法。

内存不安全示例:迭代器失效

如果在遍历一个数据结构的过程中修改这个数据结构,会导致迭代器失效。

在Rust里面,下面这样的代码是不允许编译通过的:
在这里插入图片描述为什么 Rust可以避免这个问题呢?因为Rust里面的for循环实质上是生成了一个迭代器,它一直持有一个指向容器的引用,在迭代器的生命周期内,任何对容器的修改都是无法编译通过的。类似这样:

在这里插入图片描述
在整个for循环的范围内,这个迭代器的生命周期都一直存在。

而它持有一个指向容器的引用,&型或者&mut型,根据情况而定。

迭代器的API设计是可以修改当前指向的元素,没办法修改容器本身的。

当我们想在这里对容器进行修改的时候,必然需要产生一个新的针对容器的&mut型引用,(clear方法的签名是Vec::clear(&mut self),调用clear必然产生对原Vec的amut型引用)。这是与Rust的“alias+mutation”规则相冲突的,所以编译不通过。

为什么在Rust中永远不会出现迭代器失效这样的错误?因为通过“mutation+alias”规则,就可以完全杜绝这样的现象,这个规则是Rust内存安全的根,是解决内存安全问题的灵魂。

Rust不是针对各式各样的场景,用case by case的方式来解决内存安全问题。而是通过一种统一的机制,高屋建瓴地解决这一类问题,快刀斩乱麻,直击要害。

这样的问题如果在编译阶段就能得到发现和解决,才是最合适的解决方案。在遍历容器的时候同时对容器做修改,可能出现在多线程场景,也可能出现在单线程场景。

类似这样的问题依靠GC也没办法处理。GC只关心内存的分配和释放,对于变量的读写权限是不关心的。GC在此处发挥不了什么作用。

而Rust依靠我们前面强调的“alias+mutation”规则就可以很好地解决该问题。这个思路的核心就是:如果存在多个只读的引用,是允许的;如果存在可写的引用,那么就一定不能同时存在其他的只读或者可写的引用。

大家看到这个逻辑,是不是马上联想到多线程环境下的“ReadWriteLocker”?事实也确实如此。Rust检查内存安全的核心逻辑可以理解为一个在编译阶段执行的读写锁。多个读同时存在是可以的,存在一个写的时候,其他的读写都不能同时存在。

大家还记不记得,Rust设计者总结的Rust的三大特点:一是快,二是内存安全,三是免除数据竞争。

由上面的分析可见,Rust所说的“免除数据竞争”,实际上和“内存安全”是一回事。

“免除数据竞争”可以看作多线程环境下的“内存安全”。单线程环境下的“内存安全”靠的是编译阶段的类似读写锁的机制,与多线程环境下其他语言常用的读写锁机制并无太大区别。

也正因为Rust编译器在设计上打下的良好基础,“内存安全”才能轻松地扩展到多线程环境下的“免除数据竞争”。

这两个概念其实只有一箭之隔。所以我们可以理解Java将此异常命名为“Concurrent”的真实含义——这里的“Concurrent”并不是单指多线程并发。

内存不安全示例:悬空指针

我们再使用一个例子,来继续说明为什么Rust的“mutation+alias”规则是有必要的。我们这次通过制造一个悬空指针来解释。

同样,使用动态数组类型,使用一个指针指向它的第一个元素,然后在原来的动态数组中插入数据:

在这里插入图片描述

编译不通过,错误信息为:

error:cannot borrow `arr’as mutable because it is also borrowed as immutable

我们可以看到,“mutation+alias”规则再次起了作用。在存在一个不可变引用的情况下,我们不能修改原来变量的值。

写Rust代码的时候,经常会有这样的感觉:Rust编译器极其严格,甚至到了“不近人情”的地步。

但是大部分时候却又发现,它指出来的问题的确是对我们编程有益的。对它使用越熟练,越觉得它是一个好帮手。

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

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

相关文章

使用vue快速开发一个带弹窗的Chrome插件

vue-chrome-extension-quickstart 说在前面 🎈平时我们使用Chrome插件通常都只是用来编写简单的js注入脚本,大家有没有遇到过需要插件在页面上注入一个弹窗呢?比如我们希望可以通过快捷键快速唤起ChatGPT面板或者快速唤起一个翻译面板&#x…

自动化革命:大象机器人的Mercury A1机械臂

引言 大象机器人的Mercury系列,是面向工业自动化和智能制造的新型机械臂产品线。这些机械臂不仅在设计上创新,还在材料选择上使用了碳纤维、铝合金和工程塑料等轻质强韧材料,搭载高精度谐波减速器。Mercury系列的推出,反映了大象机…

day2:TCP、UDP网络通信模型

思维导图 机械臂实现 #include <head.h> #define SER_POTR 8899 #define SER_IP "192.168.125.223" int main(int argc, const char *argv[]) {//创建套接字int cfdsocket(AF_INET,SOCK_STREAM,0);if(cfd-1){perror("");return -1;}//链接struct so…

部署MinIO

一、安装部署MINIO 1.1 下载 wget https://dl.min.io/server/minio/release/linux-arm64/minio chmod x minio mv minio /usr/local/bin/ # 控制台启动可参考如下命令, 守护进程启动请看下一个代码块 # ./minio server /data /data --console-address ":9001"1.2 配…

深度学习笔记(七)——基于Iris/MNIST数据集构建基础的分类网络算法实战

文中程序以Tensorflow-2.6.0为例 部分概念包含笔者个人理解&#xff0c;如有遗漏或错误&#xff0c;欢迎评论或私信指正。 截图和程序部分引用自北京大学机器学习公开课 认识网络的构建结构 在神经网络的构建过程中&#xff0c;都避不开以下几个步骤&#xff1a; 导入网络和依…

【Android+物联网】Android封装MQTT连接阿里云物联网平台

前言&#xff1a; 亲测可行&#xff0c;本文实现Android封装MQTT连接阿里云物联网平台。将MQTT协议和连接阿里云平台的操作通过Android studio写入APP中&#xff0c;并简单设计UI。实现手机APP远程控制单片机LED灯亮灭的功能。 关于《Android软件开发》&#xff0c;见如下专栏…

手拉手Vue3生命周期实战应用

每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实例到 DOM&#xff0c;以及在数据改变时更新 DOM。在此过程中&#xff0c;它也会运行被称为生命周期钩子的函数&#xff0c;让开发者有机会在特定阶…

SpringAMQP的使用

1. 简介&#xff1a; SpringAMQP是基于RabbitMQ封装的一套模板&#xff0c;并且还利用SpringBoot对其实现了自动装配&#xff0c;使用起来非常方便。 SpringAmqp的官方地址&#xff1a;https://spring.io/projects/spring-amqp SpringAMQP提供了三个功能&#xff1a; 自动声…

【linux】查看Debian应用程序图标对应的可执行命令

在Debian系统中&#xff0c;应用程序图标通常与.desktop文件关联。您可以通过查看.desktop文件来找到对应的可执行命令。这些文件通常位于/usr/share/applications/或~/.local/share/applications/目录下。这里是如何查找的步骤&#xff1a; 1. 打开文件管理器或终端。 2. 导…

20240115如何在线识别俄语字幕?

20240115如何在线识别俄语字幕&#xff1f; 2024/1/15 21:25 百度搜索&#xff1a;俄罗斯语 音频 在线识别 字幕 Bilibili&#xff1a;俄语AI字幕识别 音视频转文字 字幕小工具V1.2 BING&#xff1a;音视频转文字 字幕小工具V1.2 https://www.bilibili.com/video/BV1d34y1F7…

<Linux> 进程间通信

目录 前言&#xff1a; 一、进程间通信 &#xff08;一&#xff09;进程间通信目的 &#xff08;二&#xff09;进程通信的要求 &#xff08;三&#xff09;进程间通信分类 二、管道 &#xff08;一&#xff09;什么是管道 &#xff08;二&#xff09;基本原理 &#…

会声会影2024什么时间发布呢?会声会影2024会有那些新功能

近年来&#xff0c;随着科技的不断进步&#xff0c;各种软件的功能越来越强大&#xff0c;其中最为常用的莫过于视频编辑软件。而会声会影作为一款颇受欢迎的视频编辑软件&#xff0c;备受用户关注。那么&#xff0c;会声会影2024什么时间发布呢&#xff1f; 首先&#xff0c;我…

Java 使用 EasyExcel 爬取数据

一、爬取数据的基本思路 分析要爬取数据的来源 1. 查找数据来源&#xff1a;浏览器按 F12 或右键单击“检查”打开开发者工具查看数据获取时的请求地址 2. 查看接口信息&#xff1a;复制请求地址直接到浏览器地址栏输入看能不能取到数据 3. 推荐安装插件&#xff1a;FeHelper&a…

搭建知识付费小程序平台:如何避免被坑,选择最佳方案?

随着知识经济的兴起&#xff0c;知识付费已经成为一种趋势。越来越多的人开始将自己的知识和技能进行变现&#xff0c;而知识付费小程序平台则成为了一个重要的渠道。然而&#xff0c;市面上的知识付费小程序平台琳琅满目&#xff0c;其中不乏一些不良平台&#xff0c;让老实人…

高可用架构去中心化重要?

1 背景 在互联网高可用架构设计中&#xff0c;应该避免将所有的控制权都集中到一个中心服务&#xff0c;即便这个中心服务是多副本模式。 对某个中心服务&#xff08;组件&#xff09;的过渡强依赖&#xff0c;那等同于把命脉掌握在依赖方手里&#xff0c;依赖方的任何问题都可…

个性化定制的知识付费小程序,为用户提供个性化的知识服务

明理信息科技知识付费saas租户平台 随着知识经济的兴起&#xff0c;越来越多的人开始重视知识付费&#xff0c;并希望通过打造自己的知识付费平台来实现自己的知识变现。本文将介绍如何打造自己的知识付费平台&#xff0c;并从定位、内容制作、渠道推广、运营维护四个方面进行…

如何保证Kafka不丢失消息

丢失消息有 3 种不同的情况&#xff0c;针对每一种情况有不同的解决方案。 生产者丢失消息的情况消费者丢失消息的情况Kafka 弄丢了消息 生产者丢失消息的情况 生产者(Producer) 调用send方法发送消息之后&#xff0c;消息可能因为网络问题并没有发送过去。所以&#xff0c;我们…

@Controller层自定义注解拦截request请求校验

一、背景 笔者工作中遇到一个需求&#xff0c;需要开发一个注解&#xff0c;放在controller层的类或者方法上&#xff0c;用以校验请求参数中(不管是url还是body体内&#xff0c;都要检查&#xff0c;有token参数&#xff0c;且符合校验规则就放行)是否传了一个token的参数&am…

从车联网到智慧城市:智慧交通的革新之路

一、引言 1、智慧城市的概念和发展背景 智慧城市&#xff08;Smart City&#xff09;是指以信息技术为基础&#xff0c;运用信息与通信等手段&#xff0c;对城市各个核心系统各项关键数据进行感测、分析、整合和利用&#xff0c;实现对城市生活环境的感知、资源的调控&#x…

Linux下的HTTPS配置:从证书到安全连接

在当今的互联网环境中&#xff0c;数据传输的安全性越来越受到重视。HTTPS&#xff0c;作为HTTP的安全版本&#xff0c;通过使用SSL/TLS协议来加密数据传输&#xff0c;确保了数据在传输过程中的安全。在Linux环境下&#xff0c;配置HTTPS需要从证书的生成到服务器的配置进行一…