指针和引用

2026年5月11日 22:58:58

示例代码:

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 时,输出的实际是 它指向的 TDebug 表示,即自动解引用了一层(甚至多层)。

  • 如果 value 的类型是 i32(实现了 Debug),那么 r 的类型是 &i32,打印 r 会直接显示 4(假设 value=4)。
  • 这与显式写 *r 的效果在 视觉输出上完全相同,因为 *ri32,打印 i32 也是显示 4

3. 本质区别:类型不同,但输出相同

写法 r 的类型 println! 实际看到的参数类型 输出效果
println!("...", *r) &i32 i32(经解引用) 4
println!("...", r) &i32 &i32 4(因为 Debug for &i32 自动解引用)

虽然底层类型不同(一个是 i32,一个是 &i32),但因为 Debug 对引用的实现很“智能”,最终打印出来的字符串是一样的(除非 TDebug 实现有特殊行为,例如打印地址而不是值——但普通类型不会)。


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
  • 对于 &StringT 就是 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 已经是 &Stringref val 会得到 &&String。打印时由于 Debug 的自动解引用,仍然会显示 "Hello"

方案三:解引用后匹配值(需要所有权转移场景)

如果你确实需要取得 String 的所有权,那必须确保你有权移动它——例如从原来的所有者变量直接匹配,而不是从引用:

let s = String::from("Hello");
match s {        // 直接匹配 owned String
    val => println!("{}", val),   // val 获得所有权
}

5. 总结

为什么 &val 模式对 &String 报错,而对 &i32 不报错?

  • &val 模式会尝试从引用中取出背后的值
  • 对于 i32Copy),取出是复制,安全且允许。
  • 对于 String(非 Copy),取出是移动所有权,但共享引用 &String 不拥有所有权,因此被编译器禁止。

修正方法通常是不用 &val 析构,而是直接匹配变量获得引用,或者小心处理所有权。

Tags: