10分钟搞懂 Rust 的 & 怎么用

一、背景

没想到自己也开始了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
  • i32Copy 类型,编译器会自动把 &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|是模式匹配语法糖,不仅限于闭包入参,在letmatch` 等地方也能用。

  • 表达式里 & → 生成引用(let r = &n)。
  • 模式里 &** → 解构引用,得到里面的值(let &m = r|&x|match &val)。

这就是为什么在 fold 闭包里能写 |acc, &x| ------ 它帮你省去了 * 解引用的步骤。这里留一个问题,如果不加&符号,怎么改呢?

本人公众号大鱼七成饱,历史文章会在上面同步

相关推荐
京东零售技术12 小时前
理论到实战,高可用架构踩坑说明书
后端
SimonKing12 小时前
你的图片又被别人“白嫖”了?用这篇Java防盗链攻略说再见!
java·后端·程序员
Olaf_n12 小时前
SpringBoot中的监听机制
后端
Olaf_n12 小时前
SpringBoot启动流程
后端
DS小龙哥12 小时前
基于STM32设计的智能盲人辅助导航系统设计
后端
DS小龙哥12 小时前
基于华为云设计的智能宠物喂养管理系统
后端
David爱编程12 小时前
synchronized 的可重入性:避免死锁的隐藏武器
java·后端
二闹13 小时前
Python字符串格式化:谁才是真正的硬汉?
后端·python
Livingbody13 小时前
零代码实践自然语言处理 - 文本分类任务实现,以qwen模型和OpenAI SDK为例
后端
FrankYoou13 小时前
spring boot autoconfigure 自动配置的类,和手工 @configuration + @bean 本质区别
java·spring boot·后端