Rust-借用检查

这篇具有很好参考价值的文章主要介绍了Rust-借用检查。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

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

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

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

Rust-借用检查,Rust,rust,apache,开发语言
首先我们介绍一下这两个概念Alias和Mutation。

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

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

编译错误示例

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

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

示例一

Rust-借用检查,Rust,rust,apache,开发语言
以上这段代码是可以编译通过的。其中变量绑定i、p1、p2指向的是同一个变量,我们可以通过不同的Path访问同一块内存p,*p1,*p2,所以它们存在“共享”。而且它们都只有只读的权限,所以它们存在“共享”,不存在“可变”。因此它一定是安全的。

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

Rust-借用检查,Rust,rust,apache,开发语言
编译,出现了错误,错误信息为:

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

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

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

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

Rust-借用检查,Rust,rust,apache,开发语言
在pl存在的情况下,不可通过i写变量。如果这种情况可以被允许,那就会出现多个入口可以同时访问同一块内存,且都具有写权限,这就违反了Rust的“共享不可变,可变不共享”的原则。错误信息为:

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

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

Rust-借用检查,Rust,rust,apache,开发语言
编译错误信息为:

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

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

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

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

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

Rust-借用检查,Rust,rust,apache,开发语言
它有两个元素,分别可以携带String类型的信息以及i64类型的信息。

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

Rust-借用检查,Rust,rust,apache,开发语言
在这段代码中,我们用if let语法创建了一个指向内部string的指针,然后在此指针的生命周期内,再把x内部数据变成i64类型。这是典型的内存不安全的场景。

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

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

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

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

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

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

Rust-借用检查,Rust,rust,apache,开发语言
在整个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”规则是有必要的。我们这次通过制造一个悬空指针来解释。

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

Rust-借用检查,Rust,rust,apache,开发语言

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

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

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

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

但是大部分时候却又发现,它指出来的问题的确是对我们编程有益的。对它使用越熟练,越觉得它是一个好帮手。文章来源地址https://www.toymoban.com/news/detail-804258.html

到了这里,关于Rust-借用检查的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Rust in Action笔记 第四章生命周期、所有权、借用

    第四章用了一个行星通信的例子来阐述整个主题,主要角色有地面站(Ground station)、人造卫星(CubeSat),两者有不同的状态并且能互相发消息通信; Rust有类型安全(type safety)机制来检查函数的类型和返回值,如果一个自定义类型(用struct声明的)包含了非内置类型(如

    2024年02月09日
    浏览(42)
  • Rust语言从入门到入坑——(2)Rust在windows上搭建开发环境

    开始搭建一个适合在windows上运行的Rust环境。 Rust支持的程序语言很多:可详见官网介绍 本文章主要是在windowns下搭建开发环境 首先,需要安装最新版的 Rust 编译工具和 Visual Studio Code。 Rust 编译工具:https://www.rust-lang.org/zh-CN/tools/install Visual Studio Code:https://code.visualstudio.com

    2024年02月09日
    浏览(50)
  • Rust软件外包开发语言的特点

    Rust 是一种系统级编程语言,强调性能、安全性和并发性的编程语言,适用于广泛的应用领域,特别是那些需要高度可靠性和高性能的场景。下面和大家分享 Rust 语言的一些主要特点以及适用的场合,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公

    2024年02月12日
    浏览(47)
  • 【rust语言】rust多态实现方式

    学习rust当中遇到了这个问题,记录一下,不对地方望指正 多态是面向对象程序设计中的一个重要概念,指同一个行为或操作在不同实例上具有不同的行为或结果。简单来说,多态就是指同一种类型的对象,在不同的上下文中有不同的行为。多态性使得程序可以更加灵活、可

    2024年02月11日
    浏览(46)
  • C语言和Rust语言的互相调用(2)(Rust调用C)

    1.创建项目 rust调用c方式挺多的,这里采用最通俗易懂的方法,用构建脚本进行构建整个项目。 2.编辑build.rs的内容 这里的build.rs:若要创建构建脚本,我们只需在项目的根目录下添加一个 build.rs 文件即可。这样一来, Cargo 就会先编译和执行该构建脚本,然后再去构建整个项

    2024年02月02日
    浏览(49)
  • 【Rust 基础篇】Rust FFI:连接Rust与其他编程语言的桥梁

    Rust是一种以安全性和高效性著称的系统级编程语言,具有出色的性能和内存安全特性。然而,在现实世界中,我们很少有项目是完全用一种编程语言编写的。通常,我们需要在项目中使用多种编程语言,特别是在与现有代码库或底层系统交互时。为了实现跨语言的互操作性,

    2024年02月15日
    浏览(49)
  • Rust 笔记:Rust 语言中的字符串

    Rust 笔记 Rust 语言中的字符串 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343 邮箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/130876665 【介绍】:本文介绍 Rust 语言中的字符和字符串的用法。 上一节:《 Rust 语言中使用 vector(向

    2024年02月06日
    浏览(51)
  • Rust 笔记:Rust 语言中的常量与变量

    Rust 笔记 Rust 语言中的常量与变量 作者 : 李俊才 (jcLee95):https://blog.csdn.net/qq_28550263?spm=1001.2101.3001.5343 邮箱 : 291148484@163.com 本文地址 :https://blog.csdn.net/qq_28550263/article/details/130875912 【介绍】:本文介绍 Rust 语言中的常量与变量。 上一节:《 上一节标题 》 | 下一节:《

    2024年02月06日
    浏览(59)
  • 【Rust】Rust学习 第十三章Rust 中的函数式语言功能:迭代器与闭包

    Rust 的设计灵感来源于很多现存的语言和技术。其中一个显著的影响就是  函数式编程 ( functional programming )。函数式编程风格通常包含将函数作为参数值或其他函数的返回值、将函数赋值给变量以供之后执行等等。 更具体的,我们将要涉及: 闭包 ( Closures ),一个可以储

    2024年02月12日
    浏览(51)
  • Rust编程语言入门之Rust的面向对象编程特性

    Rust 受到多种编程范式的影响,包括面向对象 面向对象通常包含以下特性:命名对象、封装、继承 “设计模式四人帮”在《设计模型》中给面向对象的定义: 面向对象的程序由对象组成 对象包装了数据和操作这些数据的过程,这些过程通常被称作方法或操作 基于此定义:

    2023年04月21日
    浏览(51)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包