一、背景
没想到自己也开始了rust九九八十一难,多次被编译器搞到崩溃。最近写了一道rust算法题,遇到了&符号问题。简单说就是调用fold时候,没有加&符号,导致编译器过不了。代码如下:
ini
let nums = vec![1, 2, 3];
//nums.iter().fold(0, |acc, &x| acc + x); ❌
里面是进行累加操作,涉及到迭代器的所有权问题(直接遍历vec也有这个问题)。考虑到vec或者类型的迭代器使用太频繁了,因此总结下&符号的使用,加深下对所有权这块理解。

二、&在哪些场景使用
前面的示例只是在入参里用到了&,有时候表达式里也有,实际一共有三种场景可以用到&符号。
1、入参模式场景里加 &
scss
nums.iter().fold(0, |acc, &x| acc ^ x)
iter()
产生&i32
。nums是vec类型,默认返回引用(官方链接:doc.rust-lang.org/std/vec/str...)。- 闭包参数位置的
&x
表示「匹配到一个&i32
,把里面的i32
绑定给x
,这样可以直接解引用(不一定是fold,其他如zip,all等也是这样匹配)。 - 等价于
|acc, x_ref| acc ^ *x_ref
,与*区别下文会介绍。
这是模式匹配的 解构引用 写法。
2、表达式里加&
平常更多是这种:
ini
let a = 5;
// 在表达式里取引用
let r: &i32 = &a;
或者调用的时候:
rust
fn foo(x: &i32) { println!("{}", x); }
let a = 42;
foo(&a); // 在调用时取引用
这里的 &a
不是解构,而是生成一个引用。
3、模式匹配里加&
(不仅仅在闭包参数)
闭包参数其实也是模式匹配的一种,&
也可以出现在 let
/ match
等地方:
ini
let v = &5;
// 方式 A:手动解引用
let x = *v;
// 方式 B:用模式匹配解构引用
let &x = v; // x: i32
println!("{}", x); // 5
同理match
:
scss
let r = &Some(10);
match r {
&Some(x) => println!("value = {}", x), // 解构出值
_ => {}
}
三、什么时候用 &
,什么时候用 *
在表达式里 :&
取引用,*
解引用。
在模式里 :&
表示匹配并解构一个引用,省掉手动 *
的步骤。
scss
// 两种等价写法
nums.iter().fold(0, |acc, &x| acc ^ x) // 模式里用 & 解构
nums.iter().fold(0, |acc, x_ref| acc ^ *x_ref) // 表达式里用 * 解引用
从对照表看下区别:
语境 | 符号 | 含义 | 示例 | 说明 |
---|---|---|---|---|
表达式 | & |
取引用 | let r = &x; |
从 x 创建一个引用 &T |
* |
解引用 | let y = *r; |
从 &T 或 &mut T 得到 T 的值 |
|
模式 | & |
解构引用 | let &y = r; |
r 是 &T ,直接把里面的 T 绑定给 y |
* |
解构智能指针(如 Box) | let Box::new(y) = b; 或 let *p = val; |
从 Box<T> 或解引用模式中直接取值 |
这里出个小例子,更加直观的看下:
rust
fn main() {
let n = 10;
// ---------- 表达式 ----------
let r: &i32 = &n; // & 在表达式里:取引用
let v: i32 = *r; // * 在表达式里:解引用
println!("表达式: r = {}, v = {}", r, v);
// ---------- 模式 ----------
let &m = r; // & 在模式里:解构引用,m = 10
println!("模式: m = {}", m);
// match 里也可以用 & 来解构
match r {
&val => println!("模式 (match): val = {}", val),
}
// ---------- 闭包参数 ----------
let nums = vec![1, 2, 3];
let sum = nums.iter().fold(0, |acc, &x| acc + x); // & 在闭包参数模式里
println!("闭包模式: sum = {}", sum);
// ---------- 智能指针 ----------
let b = Box::new(99);
let v = *b; // 表达式里 *:取出 Box 里的值
println!("Box 解引用: v = {}", v);
}
四、补充解释下vec []的到的是&与 Box的区别
1、在 Rust 里,vec[index]
得到的是一个引用,而不是值拷贝。实现代码如下:
rust
impl<T> Index<usize> for Vec<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
// 返回 &T
}
}
2、为什么 let val = v[1];
可以直接得到值,不需要解引用?
这是因为:
v[1]
实际上返回&i32
。- 但
i32
是Copy
类型,编译器会自动把&i32
解引用 + 拷贝一份值出来。
如果是 非 Copy 类型 (例如 String
),你就能看到区别:
rust
fn main() {
let v = vec![String::from("hello")];
// 直接用会报错:因为 v[0] 返回 &String,而 String 不是 Copy
// let s: String = v[0]; // ❌ 报错: cannot move out of index
// 正确方式:取引用
let r: &String = &v[0]; // ✅
println!("引用: {}", r);
// 或者 clone 出一个 String
let s: String = v[0].clone(); // ✅
println!("拷贝: {}", s);
}
3、Box 本质上就是一个 智能指针,它返回的确实是指针,不过是一个"带所有权的堆指针"。
这也是为什么要对比下&。Box可以自动管理堆内存(作用域结束时自动释放),实现了 Deref
,用起来像普通引用。
rust
let reader: Box<dyn Read> = if input == "-" {
Box::new(std::io::stdin())
} else {
Box::new(File::open(input)?)
};
不用Box也可以这样实现:
ini
let stdin = std::io::stdin();
let file = File::open("foo.txt")?;
let reader: &dyn Read = &stdin; // 或 &file
引用 &T
/ &mut T
:指向栈或堆上的数据,但不拥有数据,生命周期受限。
五、总结
&
既能在表达式 里用(取引用),也能在模式匹配里用(解构引用)。
|acc, &x|是模式匹配语法糖,不仅限于闭包入参,在
let、
match` 等地方也能用。
- 表达式里
&
→ 生成引用(let r = &n
)。
- 模式里
&
** → 解构引用,得到里面的值(let &m = r
、|&x|
、match &val
)。
这就是为什么在 fold
闭包里能写 |acc, &x|
------ 它帮你省去了 *
解引用的步骤。这里留一个问题,如果不加&符号,怎么改呢?
本人公众号大鱼七成饱,历史文章会在上面同步
