borrow过去式 borrow过去分词

2024-09-2701:47:47综合资讯0

我们昨天在探讨中发现了一个重要问题:如果 data 在离开作用域后被释放,但仍有引用指向它,就会导致我们极力避免的“使用已释放内存”(use after free)问题。那么,该如何处理这种情况呢?这正是我们今天要深入探讨的主题。

要解决这个问题,必须对值的引用进行严格约束。具体来说,借用必须在值的生命周期内进行。简单来说,生命周期短的值可以借用生命周期长的,而生命周期长的值不能借用短的。

来看一个例子:

在这个例子中,main() 函数中的生命周期较长的变量 r 引用了 local_ref() 函数内生命周期较短的局部变量。在 Golang 中,a 会自动逃逸到堆上,但在 Rust 中,这样的做法会导致编译错误,因为它违反了引用约束。

接下来,我们将探讨 Rust 中的只读借用。某些情况下,我们需要在借用的过程中修改值的内容,这时就需要用到

可变借用

在引入可变借用之前,由于一个值同一时刻只能有一个所有者,因此修改值只能通过唯一的所有者。允许借用改变值本身会带来新问题。首先看

多个可变引用共存

的情况:

在这段代码中,data.iter_mut() 方法产生了 &mut 可变借用;随后在 {} 代码块内,data.push() 方法又使用了 &mut。在同一作用域内出现多个可变引用是不合法的,Rust 编译器因此阻止了这种情况。如下图所示:

borrow过去式 borrow过去分词

换句话说,在同一作用域下,多个可变引用是不被允许的,超过一次可变引用会导致编译错误。

那么,是否可以在同一作用域内同时存在一个可变引用和多个只读引用呢?

borrow过去式 borrow过去分词

从图中可以看出,仍然会出现错误。接下来,我们总结一下引用的限制:

为了确保内存安全,Rust 对可变引用的使用做了以下严格的约束:

在一个作用域内,

只能有一个活跃的可变引用

。这里的“活跃”是指实际用于修改数据的可变引用。如果只是定义而未修改数据,则不算活跃。

在一个作用域内,

活跃的可变引用和只读引用是互斥的

,不能同时存在。

用通俗的话说,就是在一个作用域内,要么存在一个可变引用(写),要么存在多个只读引用(读)。这一约束规则类似于读写锁(RwLock),可以进行类比学习。

通过对可变引用约束的了解,我们发现 Rust 不仅解决了垃圾回收(GC)能够解决的内存安全问题,还处理了 GC 无法解决的问题。在编写代码时,Rust 编译器像一个老师一样,不断提醒我们采用最佳方案以确保代码的安全。

事实上,了解数据在堆栈中的存放方式及其在内存中的访问情况,并从底层理解这些概念,才是最有效的学习途径。

最近几天,我们学习了借用语义,理清了只读引用和可变引用的原理,并结合之前学习的 Move / Copy 语义,Rust 编译器通过检查确保代码遵守这些规则:

一个值在同一时刻只能有一个所有者。当所有者离开作用域时,该值会被丢弃。赋值或传参会导致值的所有权转移,一旦所有权转移,之前的变量将无法访问。

如果值实现了 Copy trait,则赋值或传参会使用 Copy 语义,值会被按位拷贝,生成新的值。

一个值可以有多个只读引用。

一个值只能有一个活跃的可变引用。可变引用(写)与只读引用(读)是互斥的关系,就像并发下的数据读写互斥一样。

引用的生命周期不能超出值的生命周期。

快速回顾一下:

borrow过去式 borrow过去分词

随着产品需求的不断变化,有时候我们需要突破“一个值只有一个所有者”的限制。具体怎么做呢?我们明天继续学习。

老张祝大家春节快乐,身体健康,万事如意,幸福美满!