从C到Rust:Trait Deref + DerefMut 智能指针与自动解引用

0 阅读11分钟

Deref / DerefMut —— 智能指针与自动解引用

一、这个 Trait 定义了数据的什么行为


// 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() 调用:


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 中,* 操作符只对指针类型有效:


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 中方法的"调用"就是函数调用——没有自动转换

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 没有自动解引用链

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 中"模拟"智能指针——只能通过函数,没有操作符支持

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 中有隐式类型转换(如 intlong),但没有"通过解引用实现的类型转换":


// C 中不能自定义隐式转换

struct String s;

// puts(s); // ❌ 不会自动把 String 转为 char*

puts(s.data); // ✅ 必须显式访问字段

  


// 没有"自动解引用"的概念

// 所有间接访问都是显式的

3.3 方法调用的冗长


// 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 的核心作用:让包装类型可以像内部类型一样使用。


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() 调用:


// 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())

自动解引用的规则:


&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

链式自动解引用:


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 * 操作符的完整路径


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 在标准库中的应用


// 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 方法:


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 造成的效果"看起来像"面向对象继承:


// "像是" 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"关系


// 面向对象继承:

// 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 不能添加字段,只能访问方法


// 继承:子类可以添加自己的字段

// class Dog extends Animal { String breed; }

  


// Deref:不能添加字段——只能访问 Target 的方法

// String deref 到 &str——String 有自己的字段(ptr, len, cap)

// 但 &str 的字段不能"继承"给 String

// String 只是可以调用 &str 的方法

区别 3:Deref 的方法不能在子类型中被重写


// 继承:可以重写(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 正确的使用场景:实现智能指针。


// ✅ 正确: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 不应该被用来模拟继承或多态。


// ❌ 错误:用 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 链与自动解引用示例


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 的自动解引用不是无限的——它只通过 Deref trait 定义的路径,不会自动穿越结构体字段。"


// 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 —— 自动 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 是值到值的访问机制。"


// 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 用更简单的几种原语组合出需要的能力。"


// 组合:

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 的核心作用


Deref:让类型"表现得像"对另一种类型的引用

智能指针(Box、Rc、Arc)→ 内部类型

拥有者类型(StringVec)→ 引用类型(&str、&[T])

  


自动解引用(Deref Coercion):

&Box<T> → &T(一层)

&Box<String> → &str(多层)

方法调用时自动搜索 deref 链

8.2 Deref != 继承

Deref继承
关系"可以当作指针访问""is-a"
方法来源自动解引用到 Target从父类继承
重写不能可以
字段访问不能访问 Target 的字段可以访问父类字段
子类型赋值String 不是 strDogAnimal
用途智能指针、借用代码复用、多态

8.3 C 中没有的东西


// C 中没有:

// 1. 智能指针——所有指针都是裸的

// 2. 自动解引用——* 只对指针有效

// 3. 方法调用的自动 deref——s.len() 这种语法不存在

// 4. 自定义 `*` 操作符的行为

// 5. 通过 Deref 实现的"方法共享"


// Rust 中有:

// 1. 智能指针(Box、Rc、Arc)——自动解引用

// 2. 拥有者类型(String、Vec)——通过 Deref 共享方法

// 3. 自动解引用链——编译器自动插入 deref 调用

// 4. 自定义 `*` 操作符——通过实现 Deref trait

// 5. 方法调用时的自动类型转换

8.4 一句话总结

C 中 * 是操作符——只对指针有效,且不可自定义。Rust 的 Deref trait 让 * 可以被任何类型实现——Box<T>Rc<T>StringVec<T> 都可以通过自动解引用表现得像内部类型的引用。这常常被误认为是"面向对象继承"——但它是不同的东西:继承是类型间的"is-a"关系,Deref 是值到值的"可以当作指针访问"机制。Rust 不提供继承,而是用组合、Trait、Enum 覆盖了继承的使用场景,Deref 只承担"智能指针"这一件事。