找回密码
立即注册
搜索
发回帖 发新帖

6501

积分

0

好友

796

主题
发表于 10 小时前 | 查看: 5| 回复: 0

本篇导读

第二卷开始,我们进入 Rust 指针的世界。前面大量使用的 &T 只是普通引用,它借用数据、不拥有所有权。而智能指针(smart pointers)不仅指向数据,还拥有数据、携带额外元数据和能力,离开作用域时会像普通值一样自动清理——这个概念源自 C++。

本篇先学最基础的 Box<T>:把值放到堆上。接着研究两个支撑智能指针行为的 trait:Deref(让指针能像引用一样用 * 解引用,并支持强制转换)和 Drop(自定义资源释放)。最后用 Box 解决递归类型的大小问题。

一、什么是智能指针

在 Rust 中,常见的智能指针包括:Box<T>、Rc<T>、RefCell<T>。其实 String 和 Vec<T> 也属于智能指针——它们拥有堆数据、带元数据、在 drop 时释放内存。

智能指针通常用结构体实现,区别于普通结构体的关键是:它们实现了 Deref 和 Drop 两个 trait。

二、Box<T>:把值放到堆上

Box<T> 是最简单的智能指针,它把一个值分配在堆上,栈上只留一个指针。

1. 基本用法

fn main() {
    let b = Box::new(5); // 5 被分配在堆上
    println!("b = {b}");
} // b 离开作用域:先 drop Box(释放堆上的 5)

单独为一个整数装箱意义不大(栈上更快)。Box 主要用于三种场景。

2. 场景一:编译期大小未知的递归类型

Rust 必须在编译期知道每个类型占多大空间,但递归类型(如链表、树)理论上大小无限。Box 大小固定(一个指针),用它“间接”存放递归部分即可。

以经典的 cons list(函数式语言链表)为例:

enum List {
    Cons(i32, Box<List>),
    Nil,
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
}

Box 只占一个指针的位置,于是编译器能确定 List 的大小。没有 Box,编译器会报 "infinite size" 错误,因为它试图为无限递归的类型分配空间。

3. 场景二:转移大量数据的所有权,避免拷贝

当你有一块很大的数据,想转移所有权但又不希望在栈上复制,装箱后只需拷贝一个指针。

4. 场景三:拥有 trait 对象

第 11 篇的 Box<dyn Trait> 用 Box 承载动态分发的值。

三、Deref trait:自定义解引用行为

1. 普通引用的解引用

let x = 5;
let y = &x;
assert_eq!(5, *y); // * 顺着引用找到值

2. Box 也能解引用

let x = 5;
let y = Box::new(x);
assert_eq!(5, *y); // Box 实现了 Deref

3. 实现自己的智能指针

定义一个类似 Box 的元组结构体 MyBox,实现 Deref:

use std::ops::Deref;

struct MyBox<T>(T);

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

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

    fn deref(&self) -> &Self::Target {
        &self.0 // 返回内部值的引用
    }
}

fn main() {
    let y = MyBox::new(5);
    assert_eq!(5, *y); // *(y.deref())
}
  • type Target = T 定义 Deref 关联类型;
  • deref 返回内部值的引用;
  • *y 实际是 *(y.deref())。

4. 函数和方法的 Deref 强制转换

Deref coercion(解引用强制转换)是 Rust 的便利机制:当你传一个类型,但函数需要引用另一类型时,Rust 自动插入 deref 调用进行转换。

fn hello(name: &str) {
    println!("你好,{name}");
}

fn main() {
    let m = MyBox::new(String::from("Rust"));
    hello(&m);
    // &MyBox<String> 自动 deref 成 &String,再 deref 成 &str
}

没有这个机制,你得写 hello(&(*m)[..]);。强制转换发生在编译期、没有运行时开销。

5. Deref 强制转换的三种情形

  • 从 &T 到 &U(T: Deref<Target=U>);
  • 从 &mut T 到 &mut U(T: DerefMut<Target=U>);
  • 从 &mut T 到 &U(可变借用可降级为共享,但反过来不行)。

四、Drop trait:资源清理的时机

Drop trait 让你指定值离开作用域时该做什么,常用于释放文件、网络连接、锁、内存等资源。

struct CustomSmartPointer {
    data: String,
}

impl Drop for CustomSmartPointer {
    fn drop(&mut self) {
        println!("清理 CustomSmartPointer 的数据:{}", self.data);
    }
}

fn main() {
    let c = CustomSmartPointer { data: String::from("我的东西") };
    let d = CustomSmartPointer { data: String::from("别的东西") };
    println!("两个智能指针已创建");
} // 逆序 drop:先 d 后 c

输出顺序:

两个智能指针已创建
清理 CustomSmartPointer 的数据:别的东西
清理 CustomSmartPointer 的数据:我的东西

注意 drop 顺序与创建顺序相反(栈式)。

提前丢弃:std::mem::drop

你不能手动调用 c.drop()(会导致 double free),但可以用标准库函数 drop(c) 提前强制释放:

let c = CustomSmartPointer { data: String::from("提前结束") };
drop(c); // 立即清理
println!("后面的代码");

std::mem::drop 本质就是拿走所有权、什么都不做,让值在函数结束时被 drop。

五、综合示例:用 Box 实现一个简单二叉树

#[derive(Debug)]
struct Tree {
    value: i32,
    left: Option<Box<Tree>>,
    right: Option<Box<Tree>>,
}

impl Tree {
    fn leaf(value: i32) -> Box<Tree> {
        Box::new(Tree { value, left: None, right: None })
    }

    fn new(value: i32, left: Option<Box<Tree>>, right: Option<Box<Tree>>) -> Box<Tree> {
        Box::new(Tree { value, left, right })
    }

    fn contains(&self, target: i32) -> bool {
        if self.value == target {
            return true;
        }
        let in_left = self.left.as_ref().map_or(false, |n| n.contains(target));
        let in_right = self.right.as_ref().map_or(false, |n| n.contains(target));
        in_left || in_right
    }
}

fn main() {
    let root = Tree::new(
        5,
        Some(Tree::leaf(3)),
        Some(Tree::new(
            8,
            Some(Tree::leaf(7)),
            None,
        )),
    );

    println!("包含 7 吗?{}", root.contains(7)); // true
    println!("包含 10 吗?{}", root.contains(10)); // false
    println!("{root:#?}");
}

这展示了 Box 在树形递归结构中的标准用法:Option<Box<Tree>> 表示可能为空的子节点,Box 提供固定大小、并在整棵树 drop 时递归释放全部节点。

六、本篇小结

  1. 智能指针拥有数据、实现 Deref 与 Drop;String、Vec 也是智能指针;
  2. Box<T> 在堆上分配值,用于递归类型、转移大数据、trait 对象;
  3. Deref trait 让自定义指针可用 *,deref 返回内部值引用;
  4. Deref 强制转换在函数/方法调用时自动把 &MyBox<String> 转成 &str,零开销;
  5. Drop trait 自定义离开作用域的清理逻辑,逆序执行;提前释放用 std::mem::drop,不能手动调用 drop 方法;
  6. 递归类型用 Box(或其他固定大小指针)间接存放,解决无限尺寸问题。

七、动手练习

  1. 用 Box 创建一个 cons list 并实现一个递归函数求所有元素之和;
  2. 实现自己的 MyBox,验证 Deref 强制转换能传给 &str 参数;
  3. 实现 Drop 的结构体,在多个实例场景观察 drop 的逆序;
  4. 用 std::mem::drop 提前释放一个值,并验证之后无法使用;
  5. (选做)用 Box 实现一个单链表,支持 push、pop、len(第 17 篇后可再用 Rc 改造)。

下篇预告:第 17 篇|智能指针(下):Rc、RefCell 与 Weak

Box 是独占所有权的。但有时一份数据需要有多个所有者(如图结构中被多处引用的节点),有时又需要在持有不可变引用的情况下修改内部数据(内部可变性)。

下一篇我们学习引用计数指针 Rc<T>、内部可变性 RefCell<T>,以及用 Weak<T> 打破引用循环导致的内存泄漏。

如果你在 Rust 学习中遇到问题,欢迎到 云栈社区 与其他开发者一起交流讨论。




上一篇:Rust闭包与迭代器:Fn / FnMut / FnOnce 与零成本抽象
下一篇:Rust智能指针进阶:Rc、RefCell、Weak多所有者与内部可变性实战
您需要登录后才可以回帖 登录 | 立即注册

关闭

站长推荐上一条 /1 下一条

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-10-28 14:16 , Processed in 0.079988 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表