0x00 开篇
本篇文章将介绍 Rust 的引用,这也是一种缓和所有权的方法。我们已经在前面见过这个 &
这个符号,尤其是在使用字符串字面量的时候,我们会使用 &str
。那么本篇文章将正式的认识一下它 &
。本篇文章的阅读时间大约 8 分钟。
0x01 引用(Reference)与借用(Borrow)
当我们在使用某些方法或者函数时,并不想把所有权转移到方法或者函数内部,Rust 则提供了一个新的概念——借用。借用的意思就是每次要执行某个方法或者函数,我把变量先借给你用一下,等你执行结束,你还要还回来。有句俗话不是说好借好还,再借不难吗。另外,变量在借用的时候,起码要显而易见吧,让编译器和读者都要明白,所以我们就使用 &
操作符。简单来讲,“引用”表示在代码层面可见的语法,而“借用”则表示代码在这个过程中的整个行为流程。
0x02 可变引用和共享引用
再来了解两个概念——共享引用和可变引用。
- 共享引用(Shared Reference):直译为共享引用,只能读取不能修改。很多文章也称其为“不可变引用”。我们可以为一个值创建多个共享引用。如
&T
就叫做 T 的共享引用。
fn main() { // 1.共享引用 // String类型 let m = String::from("rust"); // 引用:&m 表示 对x的共享引用 let n = &m; println!("m = {}, n = {}", m, n); } // 运行结果 // x = 888, y = 888
- 可变引用(Mutable Reference):可以读取且可以修改。共享应用使用操作符
&mut
。如&mut T
就叫做 T 的可变引用。当某个值存在可变引用时,你将不能拥有该值的任何其它引用。
fn main() { let mut a = String::from("rust"); let b = &mut a; b.push_str(" is so easy!"); println!("{}", b); } // 运行结果 // rust is so easy!
【重要】:共享引用在编译时执行多读(Multiple Readers)的检查规则,可变引用在编译时执行单写(Single Writer)的检查规则。
- 如果某个值存在共享引用,该值将被锁定,无法被修改,即便是该值的所有者也将禁止修改它。
- 如果某个值存在可变引用,则该引用将拥有排他读写权。在这个可变引用的存续期间,对应值的所有者都将无法使用。
0x03 解引用
显式解引用
既然存在引用,那必然需要相反的操作——解引用。在 Rust 中,引用是通过 &
来显式创建的,那么解引用也需要显式使用 *
操作符。
fn main() { let x = 888; let y = &x; println!(" x = {}, y = {}", x, *y); } // 运行结果 // x = 888, y = 888
隐式解引用
当使用 .
操作符来调用一些方法时,Rust 会对齐进行隐式解引用。除此之外,必须使用 &
和 *
来追随引用。
fn main() { let mut a = String::from("rust"); (&mut a).push_str(" is so easy!"); println!("{}", a); } // 运行结果 // rust is so easy!
上面的代码:(&mut a).push_str(" is so easy!");
等价于 a.push_str(" is so easy!");
。其实从push_str
方法的源码(如下:)我们也可以看到,传递的是一个 &mut self
可变引用值,这里就是隐式解引用。
push_str
方法源码(string.rs)
pub fn push_str(&mut self, string: &str) { self.vec.extend_from_slice(string.as_bytes()) }
0x04 引用的内存分析
本篇文章介绍的引用其实存的仅仅是一个地址,这个地址就指向了真正的数据。大家可以通过断点自己看下堆栈信息。
0x05 小结
本篇文章简单介绍了引用,算是一个开篇。务必要记住并且理解借用行为的规则。
- 任何引用的作用域必须小于其引用原值的作用域
- 同一作用域中不能同时存在可变引用和共享引用,允许同时有多个共享引用或者仅有一个可变引用
- 在可变引用的存续期间,无法使用对应值,即便是对应值的所有者也都将无法使用