Rust中的Deref

tianyi Lv3

常规解引用

  • Deref 可以让智能指针像引用那样工作,这样你就可以写出同时支持智能指针和引用的代码,例如 T,即*智能指针(Smart Pointer)自动解引用
  • Drop 允许你指定智能指针超出作用域后自动执行的代码,例如做一些数据清除等收尾工作,即方法自动调用(自动解引用 + 方法查找),注意自动解引用只适用在:函数传参和方法调用,而变量赋值时需要手动解引用
    在讲智能指针解引用前,我们先来看看一般的解引用。引用是对一个变量值的内存地址的引用,解引用那就是通过内存地址获取到值。
    1
    2
    3
    4
    5
    6
    7
    fn main() {
    let x = 5;
    let y = &x;

    assert_eq!(5, x);
    assert_eq!(5, *y);
    }
    y 就是一个常规引用,包含了值 5 所在的内存地址,然后通过解引用 *y,我们获取到了值 5。

智能指针解引用

当引用的是一个值的时候,我们可以直接 “*y”解引用拿到内存地址上的值,那如果引用的是一个结构体的话,直接进行解引用,编译器可能会束手无策,因此我们可以为智能指针结构体实现Deref特征。

实现 Deref 后的智能指针结构体(智能指针往往是基于结构体实现,它与我们自定义的结构体最大的区别在于它实现了 Deref 和 Drop 特征),就可以像普通引用一样,通过 * 进行解引用,例如 Box 智能指针。

1
2
3
4
fn main() {
let x = Box::new(1);
let sum = *x + 1;
}

智能指针 x 被 * 解引用为 i32 类型的值 1,然后再进行求和。

我们可以来定义自己的智能指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct MyBox<T>(T);

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
// 为智能指针实现 Deref 特征
use std::ops::Deref;

impl<T> Deref for MyBox<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.0
}
}

当解引用 MyBox 智能指针时,返回元组结构体中的元素 &self.0

“ * “ 背后的原理

当我们使用智能指针Box进行解引用的时候,实际上是执行了

1
*(y.deref())

deref 方法返回值的常规引用,然后通过 * 对常规引用进行解引用,最终获取到目标值。

为什么要用这种看起来麻烦的方法,原因在于所有权系统的存在。如果 deref 方法直接返回一个值,而不是引用,那么该值的所有权将被转移给调用者,而我们不希望调用者仅仅只是 *T 一下,就拿走了智能指针中包含的值。

函数和方法中的Deref隐式转换(方法的自动调用)

对于函数的传参,rust实现了一个及其方便的隐式转换:Deref 隐式转换。若一个类型实现了Deref 特征,那它的引用在传给函数或方法时,会根据参数签名来决定是否进行隐式的 Deref 转换,例如:

1
2
3
4
5
6
7
8
fn main() {
let s = String::from("hello world");
display(&s)
}

fn display(s: &str) {
println!("{}",s);
}

这个有几点需要注意:

  • String 实现了 Deref 特征,可以在需要时自动被转换为 &str 类型
  • &s 是一个 &String 类型,当它被传给 display 函数时,自动通过 Deref 转换成了 &str
  • 必须使用 &s 的方式来触发 Deref(仅引用类型的实参才会触发自动解引用)

连续的隐式 Deref 转换

Deref不仅仅可以进行一次隐式转换,还可以进行连续的隐式 Deref 转换,如下:

1
2
3
4
5
6
7
8
fn main() {
let s = MyBox::new(String::from("hello world"));
display(&s)
}

fn display(s: &str) {
println!("{}",s);
}

我们使用了之前自定义的智能指针 MyBox,并将其通过连续的隐式转换变成 &str 类型:首先 MyBox 被 Deref 成 String 类型,结果并不能满足 display 函数参数的要求,编译器发现 String 还可以继续 Deref 成 &str,最终成功的匹配了函数参数。


Deref 可变引用的转换

刚刚讲的都是不可变引用的转换,实际上Deref也存在可变引用的转换,实际上,Deref的转换规则有三个:

  • 当 T: Deref<Target=U>,可以将 &T 转换成 &U,也就是我们之前看到的例子
  • 当 T: DerefMut<Target=U>,可以将 &mut T 转换成 &mut U
  • 当 T: Deref<Target=U>,可以将 &mut T 转换成 &U

关于DerefMut的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
struct MyBox<T> {
v: T,
}

impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox { v: x }
}
}

use std::ops::Deref;

impl<T> Deref for MyBox<T> {
type Target = T;

fn deref(&self) -> &Self::Target {
&self.v
}
}

use std::ops::DerefMut;

impl<T> DerefMut for MyBox<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.v
}
}

fn main() {
let mut s = MyBox::new(String::from("hello, "));
display(&mut s)
}

fn display(s: &mut String) {
s.push_str("world");
println!("{}", s);
}

总结

一个类型为 T 的对象 foo,如果 T: Deref<Target=U>,那么,相关 foo 的引用 &foo 在应用的时候会自动转换为 &U

  • Title: Rust中的Deref
  • Author: tianyi
  • Created at : 2025-08-06 17:28:32
  • Updated at : 2025-08-09 10:35:59
  • Link: https://github.com/ztygod/2025/08/06/Rust中的Deref/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments