Deref / DerefMut ------ 智能指针与自动解引用
一、这个 Trait 定义了数据的什么行为
rust
// Deref trait 的定义
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
// DerefMut trait 的定义
pub trait DerefMut: Deref {
fn deref_mut(&mut self) -> &mut Self::Target;
}
Deref 的含义:"我是一个智能指针------你可以自动把我变成内部数据的引用。"
当你对一个实现了 Deref 的类型使用 * 或 . 时,编译器会自动插入 deref() 调用:
rust
let b = Box::new(42);
// *b 展开为 *(b.deref())
// b.deref() 返回 &i32
// *(&i32) 得到 i32 值
let val: i32 = *b; // 42------通过 Deref 自动解引用
// 方法调用时也会自动解引用
let s = String::from("hello");
// s.len() 展开为 s.deref().len()
// String::deref() 返回 &str
// str::len() 被调用了
二、C 中是什么
2.1 C 的 * 是硬编码的
在 C 中,* 操作符只对指针类型有效:
c
int x = 42;
int* p = &x;
int y = *p; // ✅ 解引用指针------语言内置
// C 中没有"智能指针"的概念
// 无法让自定义类型支持 * 操作符
// 所有间接引用都只能通过显式的函数调用
struct Box {
int* ptr;
};
struct Box b = { malloc(sizeof(int)) };
*b.ptr = 42; // ❌ 不能写 *b------必须写 *b.ptr
// 需要显式的函数:
int box_get(struct Box* b) { return *b->ptr; }
void box_set(struct Box* b, int val) { *b->ptr = val; }
2.2 C 的方法调用需要显式函数
c
// C 中方法的"调用"就是函数调用------没有自动转换
struct String {
char* data;
size_t len;
};
// 方法定义
size_t string_len(const struct String* s) {
return s->len;
}
// 调用
struct String s = { "hello", 5 };
size_t len = string_len(&s); // 显式传参
// 没有 "s.len()" 或 "s->len()" 的自动解引用
2.3 C 中没有自动类型转换链
c
// C 没有自动解引用链
struct A { int x; };
struct B { struct A* a; };
struct C { struct B* b; };
struct C c = { ... };
// 要访问 x:
c.b->a->x; // 显式手动追踪
// 没有自动让你从 C 直接访问 A 的机制
三、C 的问题
3.1 没有"智能指针"的抽象
C 中所有指针都是裸指针------没有所有权语义、没有自动解引用、没有安全保证:
c
// C 中"模拟"智能指针------只能通过函数,没有操作符支持
struct Rc {
int* ptr;
int* refcount;
};
// 必须用函数调用来"装得像智能指针"
int rc_get(struct Rc* r) { return *r->ptr; }
void rc_set(struct Rc* r, int val) { *r->ptr = val; }
// rc_get 和 rc_set------每次调用都要显式写出
// 不能像 Box 那样直接 *box
3.2 没有"自动"的隐式转换
C 中有隐式类型转换(如 int → long),但没有"通过解引用实现的类型转换":
c
// C 中不能自定义隐式转换
struct String s;
// puts(s); // ❌ 不会自动把 String 转为 char*
puts(s.data); // ✅ 必须显式访问字段
// 没有"自动解引用"的概念
// 所有间接访问都是显式的
3.3 方法调用的冗长
c
// C 的方法调用风格------所有操作都通过命名函数
vec_push(&vec, 1); // 推入一个元素
vec_pop(&vec); // 弹出一个元素
vec_get(&vec, 0); // 获取元素
// 对比 Rust------通过 Deref 优雅
vec.push(1); // 自动 &mut vec
vec.pop();
vec[0];
四、Rust 为什么需要这个 Trait
4.1 让"智能指针"表现得像指针
Deref 的核心作用:让包装类型可以像内部类型一样使用。
rust
use std::ops::Deref;
struct MyBox<T>(T);
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> DerefMut for MyBox<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
// 现在 MyBox 表现得像一个指针:
let mut mb = MyBox(42);
*mb = 43; // ✅ 通过 DerefMut
println!("{}", *mb); // ✅ 通过 Deref
4.2 自动解引用(Deref Coercion)
Rust 编译器在以下情况自动插入 deref() 调用:
rust
// 1. 显式解引用
let b = Box::new(42);
let val: i32 = *b; // 编译为 *(b.deref())
// 2. 方法调用
let s = String::from("hello");
let c = s.chars(); // String 没有 chars() 方法
// 编译器自动尝试:
// s.deref().chars() ← &str 有 chars() 方法
// 找到了!编译通过。
// 3. 函数参数传递
fn takes_str(s: &str) {}
let s = String::from("hello");
takes_str(&s); // &String → 自动 deref → &str
// 编译为 takes_str(s.deref())
自动解引用的规则:
r
&T → 如果 T: Deref<Target=U>,则 &T 可以自动转为 &U
&mut T → 如果 T: Deref<Target=U>,则 &mut T 可以自动转为 &U
&mut T → 如果 T: DerefMut<Target=U>,则 &mut T 可以自动转为 &mut U
链式自动解引用:
rust
fn takes_str(s: &str) {}
let s = String::from("hello");
takes_str(&s);
// 编译器自动插入:
// takes_str(s.deref()) ← String → &str
let b = Box::new(String::from("hello"));
takes_str(&b);
// 编译器自动插入多层 deref:
// takes_str(b.deref().deref()) ← Box<String> → String → &str
4.3 * 操作符的完整路径
rust
let b = Box::new(42);
// *b 的完整展开路径:
// 1. 编译器看到 *b
// 2. b 的类型是 Box<i32>
// 3. Box<i32> 实现了 Deref<Target=i32>
// 4. 编译器插入 *(b.deref())
// 5. b.deref() 返回 &i32
// 6. *(&i32) 得到 i32(一个整数)
// 等价于:
let val = *(b.deref()); // 显式写法
4.4 Deref 在标准库中的应用
rust
// Box<T> → &T
let b = Box::new(42);
let r: &i32 = &b; // 自动 deref
// Rc<T> → &T
let r = Rc::new(42);
let r: &i32 = &r; // 自动 deref
// Arc<T> → &T
let a = Arc::new(42);
let r: &i32 = &a; // 自动 deref
// String → &str
let s = String::from("hello");
let r: &str = &s; // 自动 deref
// Vec<T> → &[T]
let v = vec![1, 2, 3];
let r: &[i32] = &v; // 自动 deref
这是为什么 String 可以使用所有 &str 方法:
rust
let s = String::from("hello world");
// 这些方法都定义在 &str 上,不是 String 上
s.len(); // String 没有 len()------通过 deref 调用 str::len()
s.contains("hello"); // 通过 deref 调用 str::contains()
s.starts_with("h"); // 通过 deref 调用 str::starts_with()
s.trim(); // 通过 deref 调用 str::trim()
五、Deref 与面向对象继承的对比
5.1 看起来像继承
Rust 初看时,Deref 造成的效果"看起来像"面向对象继承:
rust
// "像是" String 继承了 &str
let s = String::from("hello");
s.len(); // "像是" String 继承了 str::len()
s.trim(); // "像是" String 继承了 str::trim()
// "像是" Vec 继承了 &[T]
let v = vec![1, 2, 3];
v.len(); // "像是" Vec 继承了 slice::len()
v.first(); // "像是" Vec 继承了 slice::first()
5.2 但 Deref 不是继承
有三个根本区别:
区别 1:Deref 不传递"is-a"关系
rust
// 面向对象继承:
// class Dog extends Animal
// Dog "is-a" Animal------可以赋值给 Animal 引用
// Deref 不会:
// String deref 到 &str
// 但 String 不是 &str
let s = String::from("hello");
// let r: &str = s; // ❌ 编译错误!String 和 &str 不是继承关系
let r: &str = &s; // ✅ 必须传引用------自动解引用发生在 & 上
区别 2:Deref 不能添加字段,只能访问方法
rust
// 继承:子类可以添加自己的字段
// class Dog extends Animal { String breed; }
// Deref:不能添加字段------只能访问 Target 的方法
// String deref 到 &str------String 有自己的字段(ptr, len, cap)
// 但 &str 的字段不能"继承"给 String
// String 只是可以调用 &str 的方法
区别 3:Deref 的方法不能在子类型中被重写
rust
// 继承:可以重写(override)父类的方法
// class Dog { void speak() { "woof" } }
// class Cat extends Dog { void speak() { "meow" } } // 重写
// Deref:目标类型的方法可以被调用,但不能被子类型"重写"
// String 可以调用 str::len()------但不能改变 len() 的行为
// 如果你在 String 上也定义了 len(),它会覆盖 deref 过来的 len()
// 但这种"覆盖"不是重写------而是方法解析优先选 String 自身的
5.3 什么时候用 Deref
Deref 正确的使用场景:实现智能指针。
rust
// ✅ 正确:Box 实现了 Deref------因为 Box 是一个智能指针
impl<T: ?Sized> Deref for Box<T> {
type Target = T;
fn deref(&self) -> &T { /* ... */ }
}
// ✅ 正确:Rc 实现了 Deref------因为 Rc 是一个智能指针
impl<T: ?Sized> Deref for Rc<T> {
type Target = T;
fn deref(&self) -> &T { /* ... */ }
}
// ✅ 正确:String 实现了 Deref<Target=str>
// String 是一个"拥有所有权的字符串"------它可以被当作 &str 使用
impl Deref for String {
type Target = str;
fn deref(&self) -> &str { /* ... */ }
}
5.4 什么时候不应该用 Deref
Deref 不应该被用来模拟继承或多态。
rust
// ❌ 错误:用 Deref 来模拟继承
struct Base {
x: i32,
}
impl Base {
fn base_method(&self) {}
}
struct Derived {
base: Base, // 组合
}
impl Deref for Derived {
type Target = Base;
fn deref(&self) -> &Base {
&self.base
}
}
let d = Derived { base: Base { x: 42 } };
d.base_method(); // ✅ "像是"继承了 Base
// 但这不是继承------这是组合 + Deref
// 如果 Base 是"动物",Derived 是"狗"------这不是正确的建模
Rust 社区的建议: Deref 应该只用于智能指针类型。不要用于"模拟继承"------用组合、Trait 或用 enum 来表达类型间的层级关系。
5.6 对比总结
| 维度 | 面向对象继承 | Deref(自动解引用) |
|---|---|---|
| 关系 | "is-a"------子类是父类的一种 | "可以当作"------通过指针访问内部数据 |
| 字段 | 子类可以继承和添加字段 | 不能访问成员类型的字段(只能访问方法) |
| 方法重写 | 可以 override | 不能------只能调用,不能改变行为 |
| 多态 | 运行时多态(虚函数) | 编译时确定(静态分发) |
| 新增字段 | 可以 | 不能 |
| 子类型可以赋值给父类型 | ✅ | ❌(String::from("x") 不是 &str) |
| 主要用途 | 代码复用、多态 | 智能指针、自动解引用 |
| Rust 中的替代 | Trait + 组合 | 本身就是 Deref |
六、Deref 链与自动解引用示例
rust
fn main() {
// 场景:Box<String> 需要用到 &str 的方法
let b = Box::new(String::from("hello world"));
// 调用 str::len()------需要两层 deref
// Box<String> → String → &str → str::len()
println!("{}", b.len());
// 等价于:
// b.deref() → &String
// b.deref().deref() → &str
// str::len(&s)
// 调用 str::contains()
println!("{}", b.contains("world"));
// 同样通过两层 deref
// 传参时的自动解引用
fn process(s: &str) {
println!("{}", s.len());
}
process(&b);
// &Box<String> → 自动 deref → &String → 自动 deref → &str
// 等价于:process(b.deref().deref())
}
编译器会自动搜索 deref 链,直到找到匹配的方法或类型。 搜索深度没有硬性限制,但实际上很少超过 3 层。
七、与 C 程序员的对话
"这不就是 C 中 -> 的链式解引用吗?"
C 程序员 :"C 中
c.b->a->x也是链式解引用,只不过需要手动写->。"
Rust :"语法上类似,但 Rust 的自动解引用是编译器自动插入的------你不需要知道Box<String>内部需要几层 deref 才能调用str::len()。而且更重要的是,Rust 的自动解引用不是无限的 ------它只通过Dereftrait 定义的路径,不会自动穿越结构体字段。"
c
// C ------ 手动追踪
struct A { int x; };
struct B { struct A* a; };
struct C { struct B* b; };
void process(struct C* c) {
int x = c->b->a->x; // 每一层都要手动写
}
rust
// Rust ------ 自动 deref(如果实现了 Deref)
let c = C { b: B { a: A { x: 42 } } };
// 如果 C、B、A 都实现了 Deref------编译器自动追踪
// 但实际上 Rust 不会这样做------除非是智能指针关系
"Deref 不是继承吗?我看着很像啊"
C 程序员:"String 自动拥有 str 的所有方法------这不是继承是什么?"
Rust :"它是自动解引用,不是继承。关键区别:String不是str的子类型------你不能把String赋值给&str变量(需要加&)。你不能重写str的方法。而且 String 的deref()返回的是临时&str------这不是类型间的层级关系,这是指针到数据的访问路径。继承是类型间的静态关系,Deref 是值到值的访问机制。"
rust
// Deref 的真相:
let s = String::from("hello");
let r: &str = s.deref(); // s.deref() 返回一个 &str,这个 &str
// 指向 s 内部的字节数组
// 这不是"继承"------这是"借用内部数据"
// 继承的关系:
// Dog d; Animal* a = &d; // is-a------Dog 就是 Animal
// Rust 没有这种关系
"那 Rust 用什么替代面向对象的继承?"
C 程序员:"Rust 不用继承,那代码复用怎么做?"
Rust:"用组合(struct 包含 struct)、Trait(接口共享)、enum(有限多态)。Rust 的选择是有意的------继承的缺点(脆弱的基类、菱形继承、重写错误)在大型项目中积累了很多技术债务。Rust 用更简单的几种原语组合出需要的能力。"
rust
// 组合:
struct Engine { /* ... */ }
struct Wheel { /* ... */ }
struct Car {
engine: Engine, // Car has-a Engine
wheels: [Wheel; 4], // Car has-a Wheels
}
// Trait(接口共享):
trait Drivable {
fn drive(&self);
}
impl Drivable for Car { /* ... */ }
impl Drivable for Truck { /* ... */ }
// 用 trait 实现"多态":
fn race(vehicles: &[impl Drivable]) { /* ... */ }
八、小结
8.1 Deref/DerefMut 的核心作用
rust
Deref:让类型"表现得像"对另一种类型的引用
智能指针(Box、Rc、Arc)→ 内部类型
拥有者类型(String、Vec)→ 引用类型(&str、&[T])
自动解引用(Deref Coercion):
&Box<T> → &T(一层)
&Box<String> → &str(多层)
方法调用时自动搜索 deref 链
8.2 Deref != 继承
| Deref | 继承 | |
|---|---|---|
| 关系 | "可以当作指针访问" | "is-a" |
| 方法来源 | 自动解引用到 Target | 从父类继承 |
| 重写 | 不能 | 可以 |
| 字段访问 | 不能访问 Target 的字段 | 可以访问父类字段 |
| 子类型赋值 | ❌ String 不是 str |
✅ Dog 是 Animal |
| 用途 | 智能指针、借用 | 代码复用、多态 |
8.3 C 中没有的东西
c
// C 中没有:
// 1. 智能指针------所有指针都是裸的
// 2. 自动解引用------* 只对指针有效
// 3. 方法调用的自动 deref------s.len() 这种语法不存在
// 4. 自定义 `*` 操作符的行为
// 5. 通过 Deref 实现的"方法共享"
rust
// Rust 中有:
// 1. 智能指针(Box、Rc、Arc)------自动解引用
// 2. 拥有者类型(String、Vec)------通过 Deref 共享方法
// 3. 自动解引用链------编译器自动插入 deref 调用
// 4. 自定义 `*` 操作符------通过实现 Deref trait
// 5. 方法调用时的自动类型转换
8.4 一句话总结
C 中
*是操作符------只对指针有效,且不可自定义。Rust 的Dereftrait 让*可以被任何类型实现------Box<T>、Rc<T>、String、Vec<T>都可以通过自动解引用表现得像内部类型的引用。这常常被误认为是"面向对象继承"------但它是不同的东西:继承是类型间的"is-a"关系,Deref 是值到值的"可以当作指针访问"机制。Rust 不提供继承,而是用组合、Trait、Enum 覆盖了继承的使用场景,Deref 只承担"智能指针"这一件事。