上一章的内容中我们讨论了Rust的所有权系统,当我们不想移动值的所有权时,我们可以使用引用和借用,而这正是本章想要讨论的问题。
引用(References)
引用允许你访问或修改数据而无需获取数据的所有权。在 Rust 中,有两种类型的引用:
- 不可变引用(&T):
- 它允许你读取数据,但不能修改它。
- 你可以同时拥有多个不可变引用。
- 当存在不可变引用时,不能再有可变引用。
- 可变引用(&mut T):
- 它允许你读取和修改数据。
- 同一时间,只能有一个可变引用指向同一数据。
- 当存在可变引用时,不能再有其他可变或不可变引用,为了防止写竞争或读取数据时数据改变。
如上图所示, 在多个线程中如果都存在对同一个值的引用时, 在没有锁的情况下, 对同一个变量进行多个可变引用显然是不安全的。
借用(Borrowing)
借用是指使用引用来访问数据的行为。当你创建一个引用时,实际上是在“借用”值。借用规则如下:
- 数据的所有者不变:当你借用一个值时,该值的所有权不会转移。
- 作用域和生命周期:引用必须在其所指向的数据的作用域内有效。这就是 Rust 中的生命周期(lifetime)概念。
引用使用示例
let s1 = String::from("abc");
do_stuff(&s1);
println!("{}", s1);
fn do_stuff(s: &String) {
// do stuff
}
上面的代码中, 我们还是创建了一个do_stuff()函数, 与上一章不同的是, 我们这个函数接受的参数是一个字符串的引用, &String
前面的&
符号,表示这是一个String
类型的引用, 因此,当调用该函数时,我们传递给他的是对s1
的引用, 而s1依然保留了对它的值的所有权。do_stuff()借用了一个对值的引用,它将引用而不是值移动到了函数中, 在函数的作用域结束时, 引用被销毁,我们的借用行为也在此时结束。在函数执行完以后,我们还可以正常的使用s1, 因为其值的所有权没有被移动过。
从内存的视角出发, 当创建一个指向s1
的引用时, Rust会创建一个指向s1
的指针。 但在Rust中,我们几乎从来不会谈论指针的行为,因为Rust语言在很大程度上会自动处理他们的创建和销毁,Rust中的有一个生命周期的概念,就是用来确保指针始终有效。
Rust的编译器可以保证, 不会让用户创建一个引用的存活时间超过它引用的源数据的存活时间,并且指针永远不能指向Null。
可变与不可变引用
引用与变量一样, 默认是不可变的,这意味着,即使被引用的值是可变的,也不能直接通过引用来修改它,例如下面这段代码:
let mut s1 = String::from("abc");
do_stuff(&s1);
fn do_stuff(s: &String) {
s.insert_str(0, "Hi, "); // Error
}
虽然s1
被声明为可变的, 但是它的引用默认是不可变的,因此在函数中试图修改s1的值,编译器就会报错。想要通过引用修改被引用的值,可以将引用声明为可变引用:
let mut s1 = String::from("abc");
do_stuff(&mut s1);
fn do_stuff(s: &mut String) {
s.insert_str(0, "Hi, ");
}
注意可变引用的格式有些许特别: &mut 变量/类型
解引用
注意到了吗,在上面的例子中的函数中,修改s
的值的时候,并没有对其先解引用。我们直接使用了.
操作符来访问可变引用上的字符串函数,就像我们在对被引用的值本身进行操作一样。
在Rust中,函数或字段的.
运算符会自动对引用进行解引用,获取到最终被引用的值。这样的话,在我们使用.
运算符的时候,就不用费劲搞清楚某个变量到底是一个值,还是一个引用,甚至是一个引用的引用了。
那怎么样可以手动的进行解引用的操作呢,和大多数语言一样,在前面加上一个*
即可, 解引用操作符的优先级很低,因此需要加上( )
:
let mut s1 = String::from("abc");
do_stuff(&mut s1);
fn do_stuff(s: &mut String) {
(*s).insert_str(0, "Hi, ");
}
注意上面说的是在.
运算符的情况下, Rust会进行自动的解引用, 对于大多数的其他运算符,例如赋值运算,是需要手动进行解引用的:
let mut s1 = String::from("abc");
do_stuff(&mut s1);
fn do_stuff(s: &mut String) {
s.insert_str(0, "Hi, ");
*s = String::from("Replacement");
}
小结
本章介绍了引用和借用的含义,以及使用方法,让我们来简单回顾一下:
x // 变量
&x //变量的不可变引用
&mut x // 变量的可变引用
i32 // 类型
&i32 // 该类型的不可变引用的类型
&mut i32 // 该类型的可变引用的类型
x: &mut i32 // x是某个值的可变引用
*x // *x解引用获取到被引用的值, 可变
x: &i32 // x是某个值的不可变引用
*x // 解引用获取到被引用的值,不可变
下一章将开始介绍Rust中的结构体。