& 的各种用法

先看 & 所在的位置,再判断含义;不要只把它记成“引用”。Rust 中最常见的四种情况如下。

位置写法一句话理解
类型&T、&mut T这个位置需要一个引用
表达式前&value、&mut value从值创建引用,即借用
模式中let &x = r匹配引用,并取出其中的值
两个值之间a & b按位与运算

最实用的判断法:: 右侧通常在写类型,= 右侧通常在写表达式。

1
2
3
let number = 42;
let r: &i32 = &number;
// ^ 类型 ^ 创建引用

速查:我现在该写哪个?

目的写法所有权结果
只读地传入数据参数写 &T,调用写 &value不转移所有权
修改调用方的数据参数写 &mut T,调用写 &mut value不转移所有权,独占访问
通过引用读出值*reference访问指向的值
遍历但保留集合for item in &items逐项得到 &T
遍历并修改集合for item in &mut items逐项得到 &mut T
消费集合for item in items逐项得到 T,集合被移动

1. 类型中的 &T 与 &mut T

&T 表示不可变引用:可以读取,不能通过它修改值。&mut T 表示可变引用:可以读取和修改。它们常见于函数参数、返回值、变量标注和方法接收者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn first_byte(text: &str) -> Option<&u8> {
text.as_bytes().first()
}

struct Counter(i32);

impl Counter {
fn value(&self) -> i32 {
self.0
}

fn increment(&mut self) {
self.0 += 1;
}
}

&self 是 self: &Self 的简写;&mut self 是 self: &mut Self 的简写。Self 表示当前实现的类型,因此上例分别等价于 self: &Counter 和 self: &mut Counter。

常见类型可按“引用谁”阅读:

写法读法
&str对字符串切片的引用
&[T]对 T 切片的引用
&dyn Trait对 trait 对象的引用
&&str对 &str 的引用
&'a T有效期至少覆盖 'a 的 T 引用

2. 值前的 &value:创建不可变引用

表达式前的 & 执行借用操作。它从已有值创建引用,而不是复制值,也不转移所有权。

1
2
3
4
5
6
7
fn print_length(text: &str) {
println!("{}", text.len());
}

let text = String::from("hello");
print_length(&text);
println!("{text}");

调用处的 &text 创建引用;参数 text: &str 接收引用。String 会自动解引用为 str,因此函数应优先接收更通用的 &str,而不是 &String。

可以同时存在多个不可变引用:

1
2
3
4
let text = String::from("Rust");
let a = &text;
let b = &text;
assert_eq!(a.len() + b.len(), 8);

3. 值前的 &mut value:创建可变引用

&mut 创建可变引用。可变引用存在期间,原值不能再被其他引用访问:同一时刻要么有多个只读引用,要么有一个可变引用。

1
2
3
4
5
let mut name = String::from("Rust");
let writable = &mut name;
writable.push_str(" Lang");

assert_eq!(name, "Rust Lang");

需要通过可变引用修改基本类型或集合元素时,用 * 访问它指向的位置:

1
2
3
4
5
let mut score = 41;
let score_ref = &mut score;
*score_ref += 1;

assert_eq!(score, 42);

4. *reference:访问引用指向的值

* 是解引用运算符。& 创建或描述引用,* 沿着已有引用访问其指向的值。

1
2
3
4
let number = 5;
let number_ref = &number;

assert_eq!(*number_ref, 5);

方法调用会自动完成必要的借用或解引用,因此通常不必手写 (*number_ref).method()。但给引用指向的位置赋值时,必须显式写 *,如上一节的 *score_ref += 1。

5. 模式中的 &pattern:拆开引用

在 let、match、if let 和 for 左侧,& 属于模式。它要求待匹配的值是一个引用,并把其中的值匹配给变量。

1
2
3
4
5
let number = 42;
let number_ref = &number;
let &value = number_ref;

assert_eq!(value, 42);

这里 number_ref 的类型是 &i32,value 的类型是 i32。这需要被取出的值实现 Copy;不能用它从 &String 中移出 String 的所有权。

循环中最常见的组合是:右侧借用集合,左侧拆开元素引用。

1
2
3
4
5
6
7
let values = vec![1, 2, 3];

for &value in &values {
println!("{value}");
}

assert_eq!(values.len(), 3);

若要直接使用元素引用,不要在左侧写 &:

1
2
3
4
5
let values = vec![1_i32, 2, 3];

for value_ref in &values {
assert!(value_ref.is_positive());
}

6. &collection、&mut collection 与迭代

在 for 右侧,&collection 和 &mut collection 是借用表达式;标准集合为它们实现了迭代,因此可作为 .iter() 和 .iter_mut() 的简洁写法。

写法等价的常见写法每轮得到集合之后可用?
for x in &valuesvalues.iter()&T可以
for x in &mut valuesvalues.iter_mut()&mut T可以
for x in valuesvalues.into_iter()T通常不可以

修改元素时,循环变量本身是 &mut T,再用 * 修改元素:

1
2
3
4
5
6
7
let mut values = vec![1, 2, 3];

for value in &mut values {
*value *= 2;
}

assert_eq!(values, vec![2, 4, 6]);

7. 字面量前的 &

字面量也能直接借用,无需先绑定变量。常用于立即满足函数的引用参数。

1
2
3
4
5
6
7
8
fn print_number(number: &i32) {
println!("{number}");
}

print_number(&100);

let array: &[i32; 3] = &[1, 2, 3];
assert_eq!(array[0], 1);

字符串字面量的类型本来就是 &'static str,所以 "hello" 已经是引用;&"hello" 的类型才是 &&str。

8. 两个值之间的 a & b:按位与

当 & 位于两个表达式之间,它不是借用,而是按位与。布尔逻辑与应写 &&。

1
2
assert_eq!(0b1100 & 0b1010, 0b1000);
assert!(true && true);

最后再记一次

看到的形式应这样理解
fn f(x: &T)、let x: &T需要或声明引用类型
&x、&mut x从值创建不可变或可变引用
*x访问引用指向的值
let &x = r、for &x in iter在模式中拆开引用
for x in &items借用集合并遍历 &T
a & b按位与