💻博主现有专栏:
C51单片机(STC89C516),c语言,c++,离散数学,算法设计与分析,数据结构,Python,Java基础,MySQL,linux,基于HTML5的网页设计及应用,Rust(官方文档重点总结),jQuery,前端vue.js,Javaweb开发,Python机器学习等
🥏主页链接:
目录
除了数据被储存在堆上而不是栈上之外,box 没有性能损失。不过也没有很多额外的功能。它们多用于如下场景:
- 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候
- 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
- 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候
🎯使用Box<T>在堆上储存数据
在讨论
Box<T>
的堆存储用例之前,让我们熟悉一下语法以及如何与储存在Box<T>
中的值进行交互。fn main() { let b = Box::new(5); println!("b = {}", b); }
这里定义了变量
b
,其值是一个指向被分配在堆上的值5
的Box
。这个程序会打印出b = 5
;在这个例子中,我们可以像数据是储存在栈上的那样访问 box 中的数据。正如任何拥有数据所有权的值那样,当像b
这样的 box 在main
的末尾离开作用域时,它将被释放。这个释放过程作用于 box 本身(位于栈上)和它所指向的数据(位于堆上)。
🎯Box允许创建递归类型
递归类型(recursive type)的值可以拥有另一个同类型的值作为其自身的一部分。但是这会产生一个问题,因为 Rust 需要在编译时知道类型占用多少空间。递归类型的值嵌套理论上可以无限地进行下去,所以 Rust 不知道递归类型需要多少空间。因为 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就可以创建递归类型了。
作为一个递归类型的例子,让我们探索一下 cons list。这是一个函数式编程语言中常见的数据类型,来展示这个(递归类型)概念。除了递归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念,在任何遇到更为复杂的涉及到递归类型的场景时都很实用。
🎃cons list 的更多内容
cons list 是一个来源于 Lisp 编程语言及其方言的数据结构,它由嵌套的列表组成。它的名字来源于 Lisp 中的
cons
函数(“construct function" 的缩写),它利用两个参数来构造一个新的列表。通过对一个包含值的列表和另一个值调用cons
,可以构建由递归列表组成的 cons list。例如这里有一个包含列表 1,2,3 的 cons list 的伪代码表示,其每一个列表在一个括号中:
(1, (2, (3, Nil)))
cons list 的每一项都包含两个元素:当前项的值和下一项。其最后一项值包含一个叫做
Nil
的值且没有下一项。cons list 通过递归调用cons
函数产生。代表递归的终止条件(base case)的规范名称是Nil
,它宣布列表的终止。enum List { Cons(i32, List), Nil, }
注意:出于示例的需要我们选择实现一个只存放
i32
值的 cons list。也可以用泛型,正如第十章讲到的,来定义一个可以存放任何类型值的 cons list 类型。use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Cons(2, Cons(3, Nil))); }
第一个
Cons
储存了1
和另一个List
值。这个List
是另一个包含2
的Cons
值和下一个List
值。接着又有另一个存放了3
的Cons
值和最后一个值为Nil
的List
,非递归成员代表了列表的结尾。$ cargo run Compiling cons-list v0.1.0 (file:///projects/cons-list) error[E0072]: recursive type `List` has infinite size --> src/main.rs:1:1 | 1 | enum List { | ^^^^^^^^^ recursive type has infinite size 2 | Cons(i32, List), | ---- recursive without indirection | help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable | 2 | Cons(i32, Box<List>), | ++++ + For more information about this error, try `rustc --explain E0072`. error: could not compile `cons-list` due to previous error
🎃计算非递归类型的大小
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), }
当 Rust 需要知道要为
Message
值分配多少空间时,它可以检查每一个成员并发现Message::Quit
并不需要任何空间,Message::Move
需要足够储存两个i32
值的空间,依此类推。因为 enum 实际上只会使用其中的一个成员,所以Message
值所需的空间等于储存其最大成员的空间大小。
🎃使用Box<T>给递归类型一个已知的大小
因为 Rust 无法计算出要为定义为递归的类型分配多少空间,所以编译器给出了一个包括了有用建议的错误:
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable | 2 | Cons(i32, Box<List>), | ++++ +
在建议中,“indirection” 意味着不同于直接储存一个值,应该间接的储存一个指向值的指针。
因为
Box<T>
是一个指针,我们总是知道它需要多少空间:指针的大小并不会根据其指向的数据量而改变。这意味着可以将Box
放入Cons
成员中而不是直接存放另一个List
值。Box
会指向另一个位于堆上的List
值,而不是存放在Cons
成员中。从概念上讲,我们仍然有一个通过在其中 “存放” 其他列表创建的列表,不过现在实现这个概念的方式更像是一个项挨着另一项,而不是一项包含另一项。enum List { Cons(i32, Box<List>), Nil, } use crate::List::{Cons, Nil}; fn main() { let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil)))))); }
Cons
成员将会需要一个i32
的大小加上储存 box 指针数据的空间。Nil
成员不储存值,所以它比Cons
成员需要更少的空间。现在我们知道了任何List
值最多需要一个i32
加上 box 指针数据的大小。通过使用 box,打破了这无限递归的连锁,这样编译器就能够计算出储存List
值需要的大小了。图 15-2 展示了现在Cons
成员看起来像什么:box 只提供了间接存储和堆分配;它们并没有任何其他特殊的功能,比如我们将会见到的其他智能指针。它们也没有这些特殊功能带来的性能损失,所以它们可以用于像 cons list 这样间接存储是唯一所需功能的场景。我们还将在第十七章看到 box 的更多应用场景。
Box<T>
类型是一个智能指针,因为它实现了Deref
trait,它允许Box<T>
值被当作引用对待。当Box<T>
值离开作用域时,由于Box<T>
类型Drop
trait 的实现,box 所指向的堆数据也会被清除。这两个 trait 对于在本章余下讨论的其他智能指针所提供的功能中,将会更为重要。让我们更详细的探索一下这两个 trait。