Rust语言|深入理解Rust引用和生命周期
前言
在上一篇《深入解析Rust所有权机制》一文中笔者介绍了 Rust 的所有权机制,本文将对 Rust 所有权的使用做进一步的补充,延伸出了一种新的概念——引用和借用。
在 Rust 中允许通过引用来实现在没有完全拥有某些数据的情况下,允许函数或变量使用这些数据。
先来对引用和借用做一个概念上的定义:
- 引用(reference):是一种非拥有型指针。
- 借用(borrow):创建对某个值的引用的操作称为借用。
在下文你将对这两个概念进行更深入的理解。
引用与借用
引用和解引用
与 C++ 不同,在 Rust 中,引用是通过 &
运算符显式创建,并通过 *
运算符显式解引用。
1 | let x = 10; |
共享引用和可变引用
共享引用:允许您读取但不能修改其引用目标(默认)。
可变引用:允许您读取和修改值。
1 | let value1 = String::from("hello"); |
在使用可变引用时需要注意:除了通过将引用操作由 &value2
修改为 &mut value2
,还需要将原始声明值声明为可变,let value2 = String::from("rust");
修改为 let mut value2 = String::from("rust");
。
引用的规则
- 在变量的生命周期内,只能存在一个或多个不可变引用,不能同时存在可变引用。
- 在变量的生命周期内,只能存在一个可变引用,不能同时存在多个可变引用。
1 | fn main() { |
执行输出:
1 | error[E0502]: cannot borrow `value` as mutable because it is also borrowed as immutable |
1 | fn main() { |
执行输出:
1 | error[E0499]: cannot borrow `value` as mutable more than once at a time |
只要存在对一个值的共享引用,即使是它的拥有者也不能修改它,该值会被锁定。如果有某个值的可变引用,那么它就会独占对该值的访问权,在可变引用消失之前,即使是拥有者也无法使用该值。共享和修改保持完全分离对于内存安全至关重要,这使得 Rust 在编译期就避免数据竞争(Data Race)。
生命周期
生命周期图解定义
参考阅读
数据竞争
数据竞争(Data Race):是并发操作中的一种常见错误,它发生在多个线程或进程同时访问共享数据,并且至少其中一个是写操作。
数据竞争可由以下行为造成:
- 至少有两个并发的线程或进程同时访问同一块内存区域。
- 至少有一个线程执行写操作。
- 没有同步数据访问的机制。
数据竞争可能导致未定义的行为,在多线程或多进程环境中,由于并发操作的不确定性,可能会发生以下问题:
- 读-写问题:一个线程正在读取共享数据的同时,另一个线程正在对其进行写操作,这可能导致读取到不一致和错误的数据。
- 写-写问题:两个线程同时对相同的数据进行写操作,结果取决于执行的先后顺序,可能导致数据的丢失和错误的状态。
悬垂引用
悬垂引用(Dangling References):当程序中的引用指向已经被释放的内存区域时,就会出现悬垂引用问题。
悬垂引用会导致严重的运行时错误,包括崩溃、未定义的行为和安全性问题,这种情况通常发生在以下几种情形:
- 释放内存后继续使用:如果程序释放了某块内存,但之后仍然保留对该内存的引用,并尝试在后续代码中继续使用,就会导致悬垂引用。
1 | int *ptr = malloc(sizeof(int)); |
- 在函数返回后引用局部变量:如果一个函数返回了指向局部变量的指针或引用,而调用者在函数返回后继续使用这个指针或引用,就会导致悬垂引用。
1 | int *getLocalValue() { |
总结
本周因为要写 Rust 引用的相关内容,就把之前看到的微软的 Rust 培训教程也拿出来看了一下,发现微软的教程在基本内容这里写的是真好,十分地简单清晰明了。而且在阅读过程中我还发现了之前在 Rust 所有权理解有出错的地方,当然这个错误也写进了博客中😓。
看来知识的学习非左右参照理解不可了,甚至教材也只是偏重一方面,可能是易于理解深度不够,可能是深度足够难以理解,当然也有可能本身理解有出错或偏差争议的地方。很难说本博文对阅读者有多大的参考学习作用,最大了不起为笔者自己做个参考了。
计算机最大的权威是计算机本身,一切以自己的实践操作经验为标准,其它知识经验只能作为总结性的参考。