Rust 学习笔记:Box<T>

Rust 学习笔记:Box< T >

  • [Rust 学习笔记:Box<T\>](#Rust 学习笔记:Box<T>)
    • [Box\<T> 简介](#Box<T> 简介)
    • [使用 Box\<T\> 在堆上存储数据](#使用 Box<T> 在堆上存储数据)
    • [启用带有 box 的递归类型](#启用带有 box 的递归类型)
      • [关于 cons 列表的介绍](#关于 cons 列表的介绍)
      • 计算非递归类型的大小
      • [使用 Box\<T\> 获取大小已知的递归类型](#使用 Box<T> 获取大小已知的递归类型)

Rust 学习笔记:Box<T>

指针是在内存中包含地址的变量的一般概念。这个地址引用或"指向"其他一些数据。在 Rust 中最常见的指针类型是引用,由 & 符号表示,并借用它们所指向的值。除了引用数据之外,它们没有任何特殊功能,也没有开销。

智能指针是一种像指针一样的数据结构,但还具有额外的元数据和功能。Rust 在标准库中定义了各种智能指针,这些指针提供的功能超出了引用所提供的功能。

具有所有权和借用概念的 Rust 在引用和智能指针之间有一个额外的区别:引用只借用数据,而在许多情况下,智能指针拥有它们所指向的数据。

我们遇到了一些智能指针:String 和 Vec<T>。这两种类型都算作智能指针,因为它们拥有一些内存,并允许对其进行操作。它们还具有元数据和额外的功能或保证。例如,String 将其容量存储为元数据,并具有确保其数据始终是有效的 UTF-8 的额外能力。

智能指针通常使用结构体实现。与普通结构体不同,智能指针实现了 Deref 和Drop trait。Deref trait 允许智能指针结构体的实例表现得像引用一样,这样就可以编写代码来使用引用或智能指针。Drop trait 允许自定义当智能指针的实例超出作用域时运行的代码。

Box<T> 简介

最直接的智能指针是 Box<T>,它运行将数据存储在堆中而不是栈中,留在栈上的是指向堆数据的指针。

Box<T> 没有性能开销,但它们也没有太多额外的功能。最常在以下情况下使用它:

  • 当你的类型在编译时无法知道其大小,并且你希望在需要精确大小的上下文中使用该类型的值时

  • 当你有大量的数据,你想要转移所有权,但要确保数据不会被复制时

  • 当你想拥有一个值,你只关心它是一个实现了特定特性的类型,而不是一个特定的类型

我们将在下文中演示第一种情况。在第二种情况下,传输大量数据的所有权可能需要很长时间,因为数据是在栈上复制的。为了在这种情况下提高性能,我们可以将大量数据存储在堆中的盒子中。然后,只有少量的指针数据在栈上被复制,而它引用的数据留在堆上的一个地方。第三种情况被称为 trait 对象,后续文章将专门讨论了这个主题。

使用 Box<T> 在堆上存储数据

首先介绍 Box<T> 的语法以及如何与存储在 Box<T> 中的值进行交互。

rust 复制代码
fn main() {
    let b = Box::new(5);
    println!("b = {b}");
}

我们将变量 b 定义为具有指向值 5 的 box 的值,该值在堆上分配。这个程序将输出 b = 5。在这种情况下,我们可以访问 box 中的数据,就像我们访问栈中的数据一样。

当一个 box 超出作用域时,就像 main 语句末尾的 b 变量那样,它将被释放。对 box(存储在栈上)和它所指向的数据(存储在堆上)都进行释放。

将单个值放在堆上并不是很有用,在栈上使用单个 i32 这样的值更合适。

Box<T> 在定义类型时更有用。

启用带有 box 的递归类型

递归类型的值可以有另一个相同类型的值作为其本身的一部分。递归类型造成了一个问题,因为 Rust 需要在编译时知道一个类型占用了多少空间。然而,递归类型的值的嵌套理论上可以无限地继续下去,因此 Rust 无法知道值需要多少空间。因为 box 的大小是已知的,所以我们可以通过在递归类型定义中插入一个 box 来启用递归类型。

作为递归类型的一个示例,让我们研究一下 cons 列表。这是函数式编程语言中常见的一种数据类型。

关于 cons 列表的介绍

cons 列表是一种来自 Lisp 编程语言的数据结构,由嵌套对组成,是 Lisp 版本的链表。它的名字来自于 Lisp 中的c ons 函数(construct function 的缩写),它从它的两个参数构造一个新的 pair。通过对由一个值和另一个值组成的对调用 cons,我们可以构造由递归对组成的 cons 列表。

例如,下面是一个 cons 列表的伪代码表示,其中包含列表 1、2、3,每一对都在括号中:

复制代码
(1, (2, (3, Nil)))

cons 列表中的每一项包含两个元素:当前项的值和下一项的值。列表中的最后一项只包含一个名为 Nil 的值,没有下一项。

cons 列表不是Rust中常用的数据结构。但从本章的 cons 列表开始,我们可以探索 box 如何让我们定义递归数据类型。

下列代码包含了 cons 列表的枚举定义。

rust 复制代码
enum List {
    Cons(i32, List),
    Nil,
}

注意,这段代码还不能编译,因为 List 类型没有已知的大小,我们将对此进行演示。

尝试构建一个 cons 列表:

rust 复制代码
use crate::List::{Cons, Nil};

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

第一个 Cons 值保存 1 和另一个 List 值。这个 List 值是另一个 Cons 值,它包含 2 和另一 List 值。这个 List 值是另一个 Cons 值,它包含 3 和一个 List 值,最后是 Nil,这是表示列表结束的非递归变体。

尝试运行这段代码,报错:

错误显示 List 类型"具有无限大小"。原因是我们用递归的变量定义了 List:它直接保存自身的另一个值。因此,Rust 无法计算出它需要多少空间来存储 List 值。

让我们分析一下为什么会出现这个错误。首先,我们来看一下 Rust 如何决定存储非递归类型的值需要多少空间。

计算非递归类型的大小

以一个 Message 枚举为例:

rust 复制代码
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

为了确定为 Message 值分配多少空间,Rust 遍历每个变体,以查看哪个变体需要最多的空间。Message::Quit 不需要任何空间,Message::Move 占用两个 i32 值大小的空间,以此类推。因为只使用一个变体,所以 Message 值所需的最大空间就是存储其最大变体所需的空间。

与此形成对比的是,当 Rust 试图确定 List 枚举这样的递归类型需要多少空间时发生的情况。编译器首先查看 Cons 变量,它包含一个 i32 类型的值和一个 List 类型的值。因此,Cons 需要的空间量等于 i32 的大小加上 List 的大小。为了计算出 List 类型需要多少内存,编译器从 Cons 变量开始,这个过程无限地继续下去。

使用 Box<T> 获取大小已知的递归类型

因为 Rust 不能计算出为递归定义的类型分配多少空间,编译器给出了一个错误,并给出了这个有用的建议:

在这个建议中,间接意味着不是直接存储一个值,而是通过存储指向该值的指针来改变数据结构,从而间接存储该值。

因为 Box<T> 是一个指针,指针的大小不会根据它所指向的数据量而改变。这意味着我们可以在 Cons 变量中放入 Box<T>,而不是直接放入另一个 List 值。Box<T> 将指向下一个 List 值,该值将位于堆上,而不是在 Cons 变量中。

从概念上讲,我们仍然有一个列表,创建了包含其他列表的列表。但是这个实现现在更像是将项放在另一个项旁边,而不是放在另一个项内部。

修改代码:

rust 复制代码
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<T> 指针。Nil 不存储任何值,因此它比 Cons 变量需要更少的空间。通过使用盒子,我们打破了无限的递归链,因此编译器可以计算出存储 List 值所需的大小。

盒子只提供间接分配和堆分配,没有任何其他特殊功能,也没有这些特殊功能所带来的性能开销,因此它们在像 cons 列表这样的情况下非常有用,其中间接是我们唯一需要的特性。

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

相关推荐
love530love2 小时前
【笔记】在 MSYS2(MINGW64)中正确安装 Rust
运维·开发语言·人工智能·windows·笔记·python·rust
景天科技苑8 小时前
【Rust宏编程】Rust有关宏编程底层原理解析与应用实战
开发语言·后端·rust·rust宏·宏编程·rust宏编程
维维酱9 小时前
Rust - 消息传递
rust
Kapaseker14 小时前
Android程序员初学Rust-线程
rust
solohoho15 小时前
Rust:所有权的理解
rust
猩猩程序员15 小时前
十年下注 Rust,我期待的下一个十年
rust
Humbunklung1 天前
Rust 控制流
开发语言·算法·rust
Kapaseker2 天前
Android程序员初学Rust-错误处理
rust
用户27692024453462 天前
基于 Tauri + Vue3 的现代化新流串口调试助手 v2
前端·rust