引用与借用

引入

上一篇博客讲到,我们需要一个机制,让我们在没有拿到所有权就可以使用变量。这里引入一个“新”(其他语言也有)的概念,引用。它像是一个指针(是一个地址),可以由此访问存储于该地址的数据,与指针不同,引用确保指向某个特定类型的有效值。

引用

例如下述代码

1
2
3
4
5
6
7
8
9
10
11
fn main() {
let s1 = String::from("hello");

let len = calculate_length(&s1);

println!("The length of '{s1}' is {len}.");
}

fn calculate_length(s: &String) -> usize {
s.len()
}

这里使用了引用&s1,引用允许你使用值,但不需要获取所有权。其中内存示意图如下,

注意:引用的类型是&String,而不是String。与 C++相同,还有一个解引用的操作*

像上述代码,虽然离开函数的作用域,但是函数calculate_length并没有s1的所有权,因此并不会触发回收。

我们把创建引用的操作叫做借用(borrowing)。借用操作不能修改变量,如下代码:

1
2
3
4
5
6
7
8
9
fn main() {
let s = String::from("hello");

change(&s);
}

fn change(some_string: &String) {
some_string.push_str(", world");
}

这个代码会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
error[E0596]: cannot borrow `*some_string` as mutable, as it is behind a `&` reference
--> src/main.rs:8:5
|
8 | some_string.push_str(", world");
| ^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
7 | fn change(some_string: &mut String) {
| +++

For more information about this error, try `rustc --explain E0596`.
error: could not compile `hello_world` (bin "hello_world") due to 1 previous error

与变量默认不可变一样,引用默认也不可变。

可变引用

如果我们想要有修改变量的,只需要修改以下代码

1
2
3
4
5
6
7
8
9
fn main() {
let mut s = String::from("hello");

change(&mut s);
}

fn change(some_string: &mut String) {
some_string.push_str(", world");
}

首先要修改s为可需改的变量,然后将函数的调用以及签名改为可变&mut s以及some_string: &mut String可需改引用。

但是,可变引用还有一个限制,那就是一个变量只能有一个可变引用。例如:

1
2
3
4
5
6
let mut s = String::from("hello");

let r1 = &mut s;
let r2 = &mut s;

println!("{}, {}", r1, r2);

错误如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
error[E0499]: cannot borrow `s` as mutable more than once at a time
--> src/main.rs:5:14
|
4 | let r1 = &mut s;
| ------ first mutable borrow occurs here
5 | let r2 = &mut s;
| ^^^^^^ second mutable borrow occurs here
6 |
7 | println!("{}, {}", r1, r2);
| -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `hello_world` (bin "hello_world") due to 1 previous error

这一个设计的目的是防止同一时间对同一数据存在多个可变引用,避免数据竞争(data race)。

数据竞争原因:

  • 两个或跟多指针同时访问同一数据
  • 至少有一个指针用于写入数据
  • 没有同步数据访问的机制
    数据竞争后果:会导致未定义的行为,难以在运行时追踪,并且难以诊断和修复。

同样的,在同时使用可变引用与不可变引用的时候也会出现问题:

1
2
3
4
5
6
7
let mut s = String::from("hello");

let r1 = &s; // 没问题
let r2 = &s; // 没问题
let r3 = &mut s; // 大问题

println!("{}, {}, and {}", r1, r2, r3);

错误如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
error[E0502]: cannot borrow `s` as mutable because it is also borrowed as immutable
--> src/main.rs:6:14
|
4 | let r1 = &s; // 没问题
| -- immutable borrow occurs here
5 | let r2 = &s; // 没问题
6 | let r3 = &mut s; // 大问题
| ^^^^^^ mutable borrow occurs here
7 |
8 | println!("{}, {}, and {}", r1, r2, r3);
| -- immutable borrow later used here

For more information about this error, try `rustc --explain E0502`.
error: could not compile `hello_world` (bin "hello_world") due to 1 previous error

错误原因就是,我们不能同时拥有不可变引用和可变引用。目的是为了防止不可用借用者在使用变量时,内容突然改变。

修改以下代码,就可运行了:

1
2
3
4
5
6
7
8
9
10
fn main() {
let mut s = String::from("hello");

let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
let r3 = &mut s;

println!("{}", r3);
}

注意,一个引用的作用域是在声明到最后一次使用为止,r3 在声明的时候,r1,r2 已经不使用了,因此 r3 的声明不会报错

悬垂引用(Dangling References)

悬垂指针(Dangling pointer)是其指向的内存可能已经被分配给其他持有者。在 Rust 中,编译器会保证数据不会在其引用之前离开作用域。

示例代码如下,创建悬垂引用:

1
2
3
4
5
6
7
fn dangle() -> &String { // dangle 返回一个字符串的引用

let s = String::from("hello"); // s 是一个新字符串

&s // 返回字符串 s 的引用
} // 这里 s 离开作用域并被丢弃。其内存被释放。
// 危险!

错误如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
error[E0106]: missing lifetime specifier
--> src/main.rs:5:16
|
5 | fn dangle() -> &String {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
help: consider using the `'static` lifetime, but this is uncommon unless you're returning a borrowed value from a `const` or a `static`
|
5 | fn dangle() -> &'static String {
| +++++++
help: instead, you are more likely to want to return an owned value
|
5 - fn dangle() -> &String {
5 + fn dangle() -> String {
|

warning: unused variable: `reference_to_nothing`
--> src/main.rs:2:9
|
2 | let reference_to_nothing = dangle();
| ^^^^^^^^^^^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_reference_to_nothing`
|
= note: `#[warn(unused_variables)]` on by default

error[E0515]: cannot return reference to local variable `s`
--> src/main.rs:8:5
|
8 | &s
| ^^ returns a reference to data owned by the current function

Some errors have detailed explanations: E0106, E0515.
For more information about an error, try `rustc --explain E0106`.
warning: `hello_world` (bin "hello_world") generated 1 warning
error: could not compile `hello_world` (bin "hello_world") due to 2 previous errors; 1 warning emitted

修改的话,只要函数直接返回String就没问题

1
2
3
4
5
fn dangle() -> String {
let s = String::from("hello");

s
}

总结

引用的规则如下

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
  • 引用必须总是有效的。