示例代码:
fn main() { // 获得一个 `i32` 类型的引用。`&` 表示取引用。 let reference = &4; match reference { // 如果用 `&val` 这个模式去匹配 `reference`,就相当于做这样的比较: // `&i32`(译注:即 `reference` 的类型) // `&val`(译注:即用于匹配的模式) // ^ 我们看到,如果去掉匹配的 `&`,`i32` 应当赋给 `val`。 // 译注:因此可用 `val` 表示被 `reference` 引用的值 4。 &val => println!("Got a value via destructuring: {:?}", val), } // 如果不想用 `&`,需要在匹配前解引用。 match *reference { val => println!("Got a value via dereferencing: {:?}", val), } // 如果一开始就不用引用,会怎样? `reference` 是一个 `&` 类型,因为赋值语句 // 的右边已经是一个引用。但下面这个不是引用,因为右边不是。 let _not_a_reference = 3; // Rust 对这种情况提供了 `ref`。它更改了赋值行为,从而可以对具体值创建引用。 // 下面这行将得到一个引用。 let ref _is_a_reference = 3; // 相应地,定义两个非引用的变量,通过 `ref` 和 `ref mut` 仍可取得其引用。 let value = 5; let mut mut_value = 6; // 使用 `ref` 关键字来创建引用。 // 译注:下面的 r 是 `&i32` 类型,它像 `i32` 一样可以直接打印,因此用法上 // 似乎看不出什么区别。但读者可以把 `println!` 中的 `r` 改成 `*r`,仍然能 // 正常运行。前面例子中的 `println!` 里就不能是 `*val`,因为不能对整数解 // 引用。 match value { ref r => println!("Got a reference to a value: {:?}", r), } // 类似地使用 `ref mut`。 match mut_value { ref mut m => { // 已获得了 `mut_value` 的引用,先要解引用,才能改变它的值。 *m += 10; println!("We added 10. `mut_value`: {:?}", m); }, } }
运行结果:
Got a value via destructuring: 4 Got a value via dereferencing: 4 Got a reference to a value: 5 We added 10. `mut_value`: 16
有个疑问没有搞明白:
let value = 5; match value { ref r => println!("Got a reference to a value: {:?}", *r), }
可以运行, 但是
let reference = &4; match reference { &val => println!("Got a value via destructuring: {:?}", *val), }
就运行不了, 会报错
Rust 模式匹配中的 & 和 ref 意义相反:
& 在模式中:解构一个引用,提取出背后的值(所有权转移或复制)。 ref 在模式中:创建一个引用,绑定到匹配的值上。 - let reference = &4 的reference取到的实际是&i32, 所以匹配&val就是取对应的值, 也就是4, 而4是具体的值, 使用*来解引用是不对的.
为什么这样写也能正常打印?:
let value = 5; match value { ref r => println!("Got a reference to a value: {:?}", r), }
1. 两个版本的差异
let value = 5; match value { ref r => println!("Got a reference to a value: {:?}", *r), // 显式解引用 }
let value = 5; match value { ref r => println!("Got a reference to a value: {:?}", r), // 直接打印引用 }
2. 为什么直接打印引用 r 也可以?
关键在于 println! 的 {:?} 格式化器要求参数实现 Debug trait。
而标准库为 所有引用类型(&T 和 &mut T)都实现了 Debug,**前提是 T 本身实现了 Debug**。
具体来说标准库中有类似这样的实现(示意):
impl<T: Debug + ?Sized> Debug for &T { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { Debug::fmt(&**self, f) // 递归地打印被引用的值 } }
因此当你打印一个 &T 时,输出的实际是 它指向的 T 的 Debug 表示,即自动解引用了一层(甚至多层)。
- 如果
value 的类型是 i32(实现了 Debug),那么 r 的类型是 &i32,打印 r 会直接显示 4(假设 value=4)。 - 这与显式写
*r 的效果在 视觉输出上完全相同,因为 *r 是 i32,打印 i32 也是显示 4。
3. 本质区别:类型不同,但输出相同
| 写法 | r 的类型 | println! 实际看到的参数类型 | 输出效果 |
println!("...", *r) | &i32 | i32(经解引用) | 4 |
println!("...", r) | &i32 | &i32 | 4(因为 Debug for &i32 自动解引用) |
虽然底层类型不同(一个是 i32,一个是 &i32),但因为 Debug 对引用的实现很“智能”,最终打印出来的字符串是一样的(除非 T 的 Debug 实现有特殊行为,例如打印地址而不是值但普通类型不会)。
4. 什么时候必须显式解引用?
- 当需要 值的所有权 或 值的实际类型 进行运算时(例如
*r + 1)。 - 当你使用的 trait 没有为引用实现,但为值实现了时(例如
std::fmt::Display 通常没有对引用自动实现,所以 println!("{}", r) 可能报错,而 println!("{}", *r) 可以)。 - 当你需要精确控制解引用层数(例如遇到
&&T 时)。
对于 Debug ({:?}),由于标准库贴心地为所有引用实现了它,所以通常可以偷懒不写 *。
5. 总结
ref r 绑定了一个引用 &value。 - 直接打印
r 能工作,因为 Debug for &T 会自动打印出引用的值(类似于一次隐式解引用)。 *r 则是显式解引用得到 T 后再打印。 - 功能上两者输出相同,但前者少写一个
*,更简洁。
所以两个写法(有 * 和无 *)都是正确的,区别只在于是否显式解引用。
为什么这样写也报错?
let reference = &String::from("Hello"); match reference { &val => println!("Got a value via destructuring: {:?}", val), }
原因在于 String 没有实现 Copy trait,而示例中的 i32 是 Copy 的。
1. 先看这个代码的编译错误:
error[E0507]: cannot move out of `*reference` which is behind a shared reference --> src/main.rs:4:11 | 4 | &val => println!("...", val), | ^^^ | | | move occurs because `val` has type `String`, which does not implement the `Copy` trait | help: consider borrowing here: `&val`
2. 模式 &val 做了什么?
当你在 match 中对一个 &T 类型的值使用模式 &val:
- 它会解构这个引用,把引用背后的
T 值移动(或复制)出来绑定到 val。 - 对于
&String,T 就是 String(一个拥有堆上数据的类型)。 String 没有实现 Copy,所以 val 会获取 String 的所有权。
但是这里有个严重问题:**reference 只是一个共享引用 &String,它并不拥有这个 String 的所有权。所有权属于原本创建 String 的变量(如果存在)或者临时值。从共享引用中强行把所有权移走是绝对禁止**的,因为这会让原所有者再也无法安全地释放内存。
所以编译器报错:cannot move out of borrowed content。
3. 为什么之前的 &i32 可以工作?
let reference = &4; // &i32 match reference { &val => println!("{}", val), // 可以运行 }
i32 实现了 Copy,因此 &val 模式不会“移动”所有权,而是复制出一个新的 i32 值给 val。 - 复制一个
i32 只是复制 4 个字节,没有任何安全问题,也不影响原引用。
这正是区别所在:**Copy 类型可以安全地从引用中取出值(复制),非 Copy 类型则不允许**。
4. 如何修正非 Copy 类型的情况?
方案一:直接绑定引用(不析构)
match reference { val => println!("Got a reference: {:?}", val), // val 是 &String }
val 的类型是 &String,没有移动所有权,只是把引用复制了一份(引用本身是 Copy 的)。
方案二:使用 ref 模式(显式创建引用)
match reference { ref val => println!("Got a reference: {:?}", val), // val 是 &&String(双重引用) }
ref 从匹配的值上创建一个引用,由于 reference 已经是 &String,ref val 会得到 &&String。打印时由于 Debug 的自动解引用,仍然会显示 "Hello"。
方案三:解引用后匹配值(需要所有权转移场景)
如果你确实需要取得 String 的所有权,那必须确保你有权移动它例如从原来的所有者变量直接匹配,而不是从引用:
let s = String::from("Hello"); match s { // 直接匹配 owned String val => println!("{}", val), // val 获得所有权 }
5. 总结
为什么 &val 模式对 &String 报错,而对 &i32 不报错?
&val 模式会尝试从引用中取出背后的值。 - 对于
i32(Copy),取出是复制,安全且允许。 - 对于
String(非 Copy),取出是移动所有权,但共享引用 &String 不拥有所有权,因此被编译器禁止。
修正方法通常是不用 &val 析构,而是直接匹配变量获得引用,或者小心处理所有权。