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| ------ 它帮你省去了 * 解引用的步骤。这里留一个问题,如果不加&符号,怎么改呢?

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

相关推荐
架构师沉默6 分钟前
程序员如何避免猝死?
java·后端·架构
椰奶燕麦24 分钟前
Windows PackageManager (winget) 核心故障排错与通用修复指南
后端
zjjsctcdl1 小时前
springBoot发布https服务及调用
spring boot·后端·https
zdl6861 小时前
Spring Boot文件上传
java·spring boot·后端
世界哪有真情1 小时前
哇!绝了!原来这么简单!我的 Java 项目代码终于被 “拯救” 了!
java·后端
RMB Player2 小时前
Spring Boot 集成飞书推送超详细教程:文本消息、签名校验、封装工具类一篇搞定
java·网络·spring boot·后端·spring·飞书
重庆小透明2 小时前
【搞定面试之mysql】第三篇 mysql的锁
java·后端·mysql·面试·职场和发展
武超杰2 小时前
Spring Boot入门教程
java·spring boot·后端
IT 行者2 小时前
Spring Boot 集成 JavaMail 163邮箱配置详解
java·spring boot·后端
gelald3 小时前
JVM - 运行时内存模型
java·jvm·后端