Rust 学习笔记(25)-Box

参考章节《Rust 程序设计语言》第15.1章 使用Box <T>指向堆上的数据

什么是智能指针?

先说说我的一个直观感觉,智能指针就是一个有特殊功能的结构体,然后感兴趣可以去看看知乎对该问题的回答

Box 智能指针

最简单直接的智能指针是 box,其类型是 Box<T>box 允许你将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。

Box<T> 类型是一个智能指针,因为它实现了 Deref trait,它允许 Box<T> 值被当作引用对待。
Box<T> 值离开作用域时,由于 Box<T> 类型 也实现了 Drop trait,因此 box 所指向的堆数据也会被清除。

Box 最简单的用法

1
2
3
4
fn main() {
    let b = Box::new(5); // 通过 new 可以将数据存储在堆上,当b离开作用域时,会自动被释放
    println!("b = {}", b);
}

什么时候使用Box

书上给出了三种情况,不过我觉得不太好理解

  1. 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
  2. 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
  3. 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候 PS: 这句话我没看懂,后面这句 "而不是其具体类型" 是什么意思

先说说第一种情况

当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候

怎么理解这句话?

编译时未知大小的类型 很好理解,首先 Rust 需要在编译时知道类型占用多少空间。
需要确切大小的上下文 比如当你要定义一个递归的时候,递归是编译时不确定大小的,但定义时由于上面的条件,因此需要确切大小

一种无法在编译时知道大小的类型是递归类型,其值的一部分可以是相同类型的另一个值。这种值的嵌套理论上可以无限的进行下去,
所以 Rust 不知道递归类型需要多少空间。不过 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就可以创建递归类型了。

我们来看一个例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 定义一个枚举
enum List {
    Cons(i32, Box<List>), // 存放一个i32的值,和下一个List
    Nil,
}

// 把枚举的两个成员引入作用域
use crate::List::{Cons, Nil};

fn main() {
    // 第一个 Cons 储存了 1 和下一个 List 值。
    // 第二个 Cons 存储了 2 和下一个 List 值。
    // 第三个 Cons 存储了 3 和一个值为 Nil 的 List,
    let list = Cons(1, Cons(2, Cons(3, Nil)));
}

这段代码不能编译,原因是 List 的一个成员被定义为是递归的:它直接存放了另一个相同类型的值。这意味着 Rust 无法计算为了存放 List 值到底需要多少空间。

此时我们就可以用 box 来解决这个问题

1
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));

因为 Box<T> 是一个指针,我们总是知道它需要多少空间:指针的大小并不会根据其指向的数据量而改变。

Cons 成员将会需要一个 i32 的大小加上储存 box 指针数据的空间。Nil 成员不储存值,所以它比 Cons 成员需要更少的空间。
现在我们知道了任何 List 值最多需要一个 i32 加上 box 指针数据的大小。
通过使用 box,打破了这无限递归的连锁,这样编译器就能够计算出储存 List 值需要的大小了。

updatedupdated2024-10-012024-10-01