用Box<T>
指向堆上的数据
最直接的智能指针是一个 box,其类型是 writeBox<T>
.Box 允许您将数据存储在堆上,而不是堆栈上。什么
remains on the stack 是指向堆数据的指针。请参阅第 4 章
查看堆栈和堆之间的差异。
Box 没有性能开销,除了将其数据存储在 heap 而不是在堆栈上。但是他们没有很多额外的功能 也。在以下情况下,您将最常使用它们:
- 当您有一个类型在编译时无法知道其大小,并且您希望 在需要精确大小的上下文中使用该类型的值
- 当您拥有大量数据,并且想要转移所有权,但 确保在执行此作时不会复制数据
- 当您想要拥有一个值并且只关心它是一个类型 实现特定 trait 而不是特定类型
我们将在“使用 Boxes “部分。在 第二种情况是,转移大量数据的所有权可能需要很长时间 时间,因为数据是在堆栈上复制的。要提高性能,请在 这种情况下,我们可以将堆上的大量数据存储在一个盒子里。 然后,只有少量的指针数据在堆栈上被复制。 而它引用的数据则保留在堆上的一个位置。第三种情况是 称为 trait 对象,第 17 章专门用了一整节“使用 trait 对象,允许不同类型的值“,只是针对该主题。因此,您在这里学到的内容将再次应用于 第十七章!
使用Box<T>
在堆上存储数据
在我们讨论Box<T>
,我们将介绍
语法以及如何与存储在Box<T>
.
示例 15-1 展示了如何使用 box 来存储i32
值:
文件名: src/main.rs
fn main() { let b = Box::new(5); println!("b = {b}"); }
示例 15-1:存储i32
值在堆上使用
箱
我们定义变量b
的值为Box
指向
价值5
,该 API 在堆上分配。该程序将打印b = 5
;在
在这种情况下,我们可以访问框中的数据,就像我们这样做时一样
数据在堆栈上。就像任何自有值一样,当 Box 从
范围,作为b
在main
,则它将被解除分配。这
释放同时发生在 Box(存储在堆栈上)和 it 的数据
指向 (存储在堆上)。
在堆上放置单个值不是很有用,因此您不会使用 boxes
他们自己经常以这种方式。具有像单个i32
在
stack 中,它们默认存储在大多数
情况。让我们看一个案例,其中 box 允许我们定义类型
如果我们没有盒子,就不会被允许。
使用框启用递归类型
递归类型的值可以具有另一个相同类型的值作为 本身。递归类型会带来一个问题,因为在编译时 Rust 需要 了解类型占用多少空间。但是,的 递归类型理论上可以无限持续,所以 Rust 不知道怎么做 价值需要的很多空间。因为盒子的大小是已知的,所以我们可以启用 递归类型。
作为递归类型的一个示例,让我们探索 cons 列表。这是一个数据 类型常见于函数式编程语言中。缺点列表类型 我们将定义 is straightforward except the recursion;因此, 我们将使用的示例中的 concepts 在您进入 涉及递归类型的更复杂的情况。
有关缺点列表的更多信息
缺点列表是来自 Lisp 编程语言的一种数据结构
及其方言 和 它由嵌套对组成,并且是
链表。它的名字来源于cons
function(“construct ”的缩写
function“),它从其两个参数构造一个新的对。由
叫cons
对于由一个值和另一个值组成的对,我们可以
构造由递归对组成的 cons 列表。
例如,下面是一个 cons 列表的伪代码表示,其中包含 列表 1、2、3,每对在括号中:
(1, (2, (3, Nil)))
cons 列表中的每个项目都包含两个元素:当前项目的值
和下一项。列表中的最后一项仅包含一个名为Nil
没有下一项。cons 列表是通过递归调用cons
功能。表示递归基本情况的规范名称是Nil
.
请注意,这与第 6 章中的 “null” 或 “nil” 概念不同,
这是无效或不存在的值。
cons list 不是 Rust 中常用的数据结构。大多数时候
当你在 Rust 中有一个项目列表时,Vec<T>
是更好的选择。
其他更复杂的递归数据类型在各种情况下都很有用,
但是通过从本章的缺点列表开始,我们可以探索盒子
让我们定义一个没有太多干扰的递归数据类型。
示例 15-2 包含一个 cons 列表的枚举定义。请注意,此代码
不会编译,因为List
type 没有已知的大小,因此
我们会演示的。
文件名: src/main.rs
enum List {
Cons(i32, List),
Nil,
}
fn main() {}
示例 15-2:第一次尝试将枚举定义为
表示i32
值
注意:我们正在实施一个仅保留i32
的值
本示例的用途。我们可以使用泛型来实现它,因为我们
在第 10 章中讨论,定义一个可以存储
任何类型。
使用List
键入以存储列表1, 2, 3
将类似于
示例 15-3:
文件名: src/main.rs
enum List {
Cons(i32, List),
Nil,
}
use crate::List::{Cons, Nil};
fn main() {
let list = Cons(1, Cons(2, Cons(3, Nil)));
}
示例 15-3:使用List
enum 来存储列表1, 2, 3
第一个Cons
值保留1
和另一个List
价值。这List
value 为
另一个Cons
值,则2
和另一个List
价值。这List
价值
再来一个Cons
值,则3
以及List
value,最后是Nil
,表示列表末尾的非递归变体。
如果我们尝试编译示例 15-3 中的代码,我们会得到 示例 15-4:
$ 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 {
| ^^^^^^^^^
2 | Cons(i32, List),
| ---- recursive without indirection
|
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
2 | Cons(i32, Box<List>),
| ++++ +
error[E0391]: cycle detected when computing when `List` needs drop
--> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^
|
= note: ...which immediately requires computing when `List` needs drop again
= note: cycle used when computing whether `List` needs drop
= note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information
Some errors have detailed explanations: E0072, E0391.
For more information about an error, try `rustc --explain E0072`.
error: could not compile `cons-list` (bin "cons-list") due to 2 previous errors
示例 15-4:尝试定义 递归枚举
该错误显示此类型“具有无限大小”。原因是我们已经定义了List
具有递归的变体:它包含自身的另一个值
径直。因此,Rust 无法计算出需要多少空间来存储List
价值。让我们分析一下为什么会收到这个错误。首先,我们要看看
Rust 决定需要多少空间来存储非递归类型的值。
计算非递归类型的大小
回想一下Message
enum 我们在示例 6-2 中讨论 enum 时定义的
第 6 章中的定义:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() {}
确定要为Message
value 时,Rust 会去
查看哪个变体需要最多的空间。锈
看到那个Message::Quit
不需要任何空间,Message::Move
需要足够的
存放 2 个空间i32
值,依此类推。因为只有一个变体将是
使用,最空间 aMessage
value 将需要的是它需要的空间
存储其最大的变体。
将此与 Rust 尝试确定
递归类型(如List
示例 15-2 中的 enum 需要。编译器启动
通过查看Cons
variant 的 ID 中,它保存 type 为i32
和一个值
的类型List
.因此Cons
需要的空间量等于
一i32
加上List
.要确定有多少内存,List
type 需要,编译器会查看变体,从Cons
变体。这Cons
variant 保存 type 为i32
和类型List
,并且这个过程无限持续,如图 15-1 所示。
图 15-1:无限List
由无限Cons
变种
用Box<T>
获取具有已知大小的递归类型
因为 Rust 无法确定要递归分配多少空间 定义的类型,编译器会给出一个错误,并提供以下有用的建议:
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to break the cycle
|
2 | Cons(i32, Box<List>),
| ++++ +
在这个建议中,“间接” 意味着不是存储一个值 直接,我们应该改变数据结构来间接存储值 而是存储指向该值的指针。
因为Box<T>
是一个指针,Rust 总是知道Box<T>
needs:指针的大小不会根据其数据量而变化
指向。这意味着我们可以将Box<T>
在Cons
变体
另一个List
值。这Box<T>
将指向下一个List
值,该值将位于堆上,而不是位于Cons
变体。
从概念上讲,我们仍然有一个列表,创建时包含其他列表的列表,但是
此实现现在更像是将项彼此相邻放置
而不是彼此内部。
我们可以更改List
enum 和
的List
在示例 15-3 中,将代码转换为示例 15-5 中的代码,它将编译:
文件名: src/main.rs
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)))))); }
示例 15-5:的定义List
使用Box<T>
在
order 具有已知大小
这Cons
variant 需要i32
加上用于存储
box 的指针数据。这Nil
variant 不存储任何值,因此它需要更少的空间
比Cons
变体。我们现在知道,任何List
value 将占用
的大小i32
加上 Box 的指针数据的大小。通过使用 Box,我们
打破了无限的递归链,因此编译器可以计算出大小
它需要存储一个List
价值。图 15-2 显示了Cons
变体
现在看起来。
图 15-2:AList
不是无限大小的
因为Cons
持有Box
Box 仅提供间接寻址和堆分配;他们没有 其他特殊功能,例如我们将使用另一个智能指针看到的功能 类型。它们也没有这些特殊的 功能,因此它们在 cons 列表等情况下非常有用,其中 间接性是我们唯一需要的功能。我们将研究 box 的更多用例 在第 17 章中也是如此。
这Box<T>
type 是一个智能指针,因为它实现了Deref
特性
这允许Box<T>
值视为引用。当Box<T>
值超出范围,则 Box 指向的堆数据将被清理
up 也是因为Drop
trait 实现。这两个特征将是
更重要的是另一个智能指针提供的功能
类型,我们将在本章的其余部分讨论。让我们来探讨一下这两个特征
更详细地。
本文档由官方文档翻译而来,如有差异请以官方英文文档(https://doc.rust-lang.org/)为准