当前位置: 首页 > news >正文

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编译器极其严格,甚至到了“不近人情”的地步。

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

相关文章:

Rust-借用检查

Rust语言的核心特点是:在没有放弃对内存的直接控制力的情况下,实现了内存安全。 所谓对内存的直接控制能力,前文已经有所展示:可以自行决定内存布局,包括在栈上分配内存,还是在堆上分配内存;支…...

xcode安装及运行源码

抖音教学视频 目录 1、xcode 介绍 2、xcode 下载 3、xocde 运行ios源码 4、快捷键 1、xcode 介绍 Xcode 是运行在操作系统Mac OS X上的集成开发工具(IDE),由Apple Inc开发。Xcode是开发 macOS 和 iOS 应用程序的最快捷的方式。Xcode 具有…...

x-cmd pkg | czg - git commit 智能生成工具

目录 简介首次用户功能特点竞品和相关作品进一步探索 简介 czg 源于 commitizen/cz-cli 交互插件中 cz-git 的延伸项目,重新使用 TypeScript 编写的零依赖独立的 Node.js 命令行工具。旨在使用交互友好的方式,辅助用户生成规范的 git commit message 约…...

Go的并发练习题目

经典并发题目 现在有4个协程,分别对应编号为1,2,3,4,每秒钟就有一个协程打印自己的编号,要求编写一个程序,让输出的编号总是按照1,2,3,4,1,2,3,4这样的规律一直打印下去 type Token struct { }func newWorker(id int, ch chan Token, nextC…...

Python 网络编程之粘包问题

【一】粘包问题介绍 【1】粘包和半包 粘包: 定义: 粘包指的是发送方发送的若干个小数据包被接收方一次性接收,形成一个大的数据包。原因: 通常是因为网络底层对数据传输的优化,将多个小数据包组合成一个大的数据块一次…...

旧衣回收小程序搭建:降低企业成本,提高回收效率!

在人们环保意识提升下,旧衣回收行业受到了大众的关注,同时旧衣回收具有门槛低、利润大的优势。在我国,回收行业不仅帮助普通人就业获利,还对环保做出了较大贡献。因此,旧衣回收行业成为了当下的热门商业模式&#xff0…...

Jmeter后置处理器——JSON提取器

目录 1、简介 2、使用步骤 1)添加线程组 2)添加http请求 3) 添加JSON提取器 1、简介 JSON是一种简单的数据交换格式,允许互联网应用程序快速传输数据。JSON提取器可以从JSON格式响应数据中提取数据、简化从JSON原始数据中提取特定…...

[SWPUCTF 2022 新生赛]奇妙的MD5

[SWPUCTF 2022 新生赛]奇妙的MD5 wp 题目页面: 提示:可曾听过ctf 中一个奇妙的字符串。 奇妙的字符串 奇妙的字符串,又跟 MD5 有关,我只知道两个: 一个是 MD5 加密后弱比较等于自身,这个字符串是 0e215…...

MHFormer 论文解读

目录​​​​​​​ Multi-Hypothesis Transformer 结果 Introduction & Related work 多假设 为什么作者提出这个模型? 3.Multi-Hypothesis Transformer 3.1 Preliminary 3.2 MultiHypothesis Generation 3.3 Temporal Embedding 3.4. SelfHypothesi…...

Python列表append()函数使用详解

在Python中,列表是一种可变序列类型,可以用来存储多个元素。列表的append()函数是用于在列表末尾添加新元素的内置方法。本文将详细介绍Python列表的append()函数及其使用方法。 一、append()函数的基本语法 append()函数的语法非常简单,只…...

第08章_面向对象编程(高级)拓展练习(关键字:static,代码块,关键字:final,抽象类和抽象方法,接口,内部类,枚举类,注解,包装类)

文章目录 第08章_面向对象编程(高级)拓展练习01-关键字:static1、银行账户类2、图形类3、数组工具类4、二分查找5、二分查找6、素数7、阅读代码,分析运行结果8、阅读代码,分析运行结果 02-代码块9、阅读代码&#xff0…...

分布式光伏运维平台在提高光伏电站发电效率解决方案

摘要:伴随着能源危机和环境恶化问题的日益加重,科技工作者进一步加大对新能源的开发和利用。太阳能光伏发电作为新型清洁能源的主力军,在实际生产生活中得到了广泛的应用。然而,光伏发电效率偏低,成为制约光伏发电发展…...

2024.1.14~1.20 周内刷题总结

2024.1.14~1.20 周内刷题总结 [ABC158F] Removing Robots 题解[ABC145F] Laminate 题解[ABC254G] Elevators 题解(坑点总结)[ARC160C] Power Up 题解[ABC203F] Weed 题解Shopping时代的眼泪 [ABC158F] Removing Robots 题解 \qquad 题面 \qquad 本题的连…...

徐州数字孪生元宇宙赋能工业智能制造,助力传统制造业数字化转型

徐州数字孪生元宇宙赋能工业智能制造,助力传统制造业数字化转型。在徐州市制造业企业数字化转型的过程中,数字孪生技术的应用已经取得了显著成效。一方面,企业的生产效率得到了显著提高,产品质量也得到了有效保障。另一方面&#…...

智云谷再获资本市场青睐,完成数千万元A+轮融资

近日,深圳前海智云谷科技有限公司(以下简称“智云谷”)完成数千万元A轮融资,本轮融资由青松基金独家投资,多维资本担任独家融资财务顾问。本轮融资资金将用于扩大新技术研发投入、智能工厂扩产、加速产品交付&#xff…...

ACM论文LaTeX模板解析(三)| 文章顶部信息(Top matter))Part 1

本文收录于专栏:ACM 论文 LaTeX模板解析,本专栏将会围绕ACM 论文 LaTeX模板解析持续更新。欢迎点赞收藏关注! 文章目录 有许多命令可以设置文章的顶部信息或(计算机科学术语)元数据。它们建立了出版物名称、文章标题、…...

[GDOUCTF 2023]受不了一点

[GDOUCTF 2023]受不了一点 wp 题目代码&#xff1a; <?php error_reporting(0); header("Content-type:text/html;charsetutf-8"); if(isset($_POST[gdou])&&isset($_POST[ctf])){$b$_POST[ctf];$a$_POST[gdou];if($_POST[gdou]!$_POST[ctf] &&…...

精心挑选免费好用的api,推荐给大家

企业基本信息&#xff1a;通过公司名称/公司ID/注册号或社会统一信用代码获取企业基本信息&#xff0c;企业基本信息包括公司名称或ID、类型、成立日期、经营状态、注册资本、法人、工商注册号、统一社会信用代码、组织机构代码、纳税人识别号等字段信息。 AI绘画-Stable Diff…...

(001)window 使用 OpenObserve

文章目录 安装上传数据报错附录 安装 1.下载安装包&#xff1a; 2. window 设置环境变量&#xff1a; ZO_ETCD_COMMAND_TIMEOUT 600 ZO_ETCD_CONNECT_TIMEOUT 600 ZO_ETCD_LOCK_WAIT_TIMEOUT 600 ZO_INGEST_ALLOWED_UPTO 10000 ZO_ROOT_USER_EMAIL 422615924qq.com ZO_…...

linux发送http请求命令

一、http get请求 1、curl命令不带参 curl “http://www.baidu.com” 如果这里的URL指向的是一个文件或者一幅图都可以直接下载到本地 curl -i “http://www.baidu.com” 显示全部信息 curl -l “http://www.baidu.com” 只显示头部信息 curl -v “http://www.baidu.com”…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?

Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式与专业的 MQ&#xff08;Message Queue&#xff09;如 Kafka、RabbitMQ 进行比较&#xff0c;核心的权衡点在于&#xff1a;简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

快刀集(1): 一刀斩断视频片头广告

一刀流&#xff1a;用一个简单脚本&#xff0c;秒杀视频片头广告&#xff0c;还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农&#xff0c;平时写代码之余看看电影、补补片&#xff0c;是再正常不过的事。 电影嘛&#xff0c;要沉浸&#xff0c;…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

算法打卡第18天

从中序与后序遍历序列构造二叉树 (力扣106题) 给定两个整数数组 inorder 和 postorder &#xff0c;其中 inorder 是二叉树的中序遍历&#xff0c; postorder 是同一棵树的后序遍历&#xff0c;请你构造并返回这颗 二叉树 。 示例 1: 输入&#xff1a;inorder [9,3,15,20,7…...

在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7

在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤&#xff1a; 第一步&#xff1a; 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为&#xff1a; // 改为 v…...