Rust从入门到实战系列一百六十七:使用 Box<T> 在堆上储存数据

本文介绍了Rust中如何使用Box在堆上存储i32值,以及如何处理递归类型如conslist,通过Box间接实现。文章展示了如何定义递归枚举并遇到编译时无限大小的问题,以及为何在Rust中通常选择非递归类型如Vec而非conslist。
摘要由CSDN通过智能技术生成

在讨论 Box 的用例之前,让我们熟悉一下语法以及如何与储存在 Box 中的值进行交互。
示例 15-1 展示了如何使用 box 在堆上储存一个 i32:
文件名: src∕main.rs
fn main() {
let b = Box::new(5);
println!(“b = {}”, b);
}
示例 15-1:使用 box 在堆上储存一个 i32 值
这里定义了变量 b,其值是一个指向被分配在堆上的值 5 的 Box。这个程序会打印出 b = 5;在这个例子
中,我们可以像数据是储存在栈上的那样访问 box 中的数据。正如任何拥有数据所有权的值那样,当像
b 这样的 box 在 main 的末尾离开作用域时,它将被释放。这个释放过程作用于 box 本身(位于栈上)
和它所指向的数据(位于堆上)。
将一个单独的值存放在堆上并不是很有意义,所以像示例 15-1 这样单独使用 box 并不常见。将像单个
i32 这样的值储存在栈上,也就是其默认存放的地方在大部分使用场景中更为合适。让我们看看一个不
使用 box 时无法定义的类型的例子。
Box 允许创建递归类型
Rust 需要在编译时知道类型占用多少空间。一种无法在编译时知道大小的类型是 递归类型(recursive
type),其值的一部分可以是相同类型的另一个值。这种值的嵌套理论上可以无限的进行下去,所以 Rust
不知道递归类型需要多少空间。不过 box 有一个已知的大小,所以通过在循环类型定义中插入 box,就
可以创建递归类型了。
让我们探索一下 cons list,一个函数式编程语言中的常见类型,来展示这个(递归类型)概念。除了递
归之外,我们将要定义的 cons list 类型是很直白的,所以这个例子中的概念,在任何遇到更为复杂的涉
及到递归类型的场景时都很实用。
cons list 的更多内容
cons list 是一个来源于 Lisp 编程语言及其方言的数据结构。在 Lisp 中,cons 函数(”construct function”
的缩写)利用两个参数来构造一个新的列表,他们通常是一个单独的值和另一个列表。
cons 函数的概念涉及到更常见的函数式编程术语;” 将 x 与 y 连接” 通常意味着构建一个新的容器而将
x 的元素放在新容器的开头,其后则是容器 y 的元素。
cons list 的每一项都包含两个元素:当前项的值和下一项。其最后一项值包含一个叫做 Nil 的值
且没有下一项。cons list 通过递归调用 cons 函数产生。代表递归的终止条件(base case)的规范名
称是 Nil,它宣布列表的终止。注意这不同于第六章中的 ”null” 或 ”nil” 的概念,他们代表无效或缺失的值。
注意虽然函数式编程语言经常使用 cons list,但是它并不是一个 Rust 中常见的类型。大部分在 Rust 中
需要列表的时候,Vec 是一个更好的选择。其他更为复杂的递归数据类型 确实在 Rust 的很多场景中
很有用,不过通过以 cons list 作为开始,我们可以探索如何使用 box 毫不费力的定义一个递归数据类型。
示例 15-2 包含一个 cons list 的枚举定义。注意这还不能编译因为这个类型没有已知的大小,之后我们
会展示:
文件名: src∕main.rs
enum List {
Cons(i32, List),
Nil,
}

fn main() {}

示例 15-2:第一次尝试定义一个代表 i32 值的 cons list 数据结构的枚举
注意:出于示例的需要我们选择实现一个只存放 i32 值的 cons list。也可以用泛型,正如第十章讲
到的,来定义一个可以存放任何类型值的 cons list 类型。
使用这个 cons 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 枚举储存列表 1, 2, 3
第一个 Cons 储存了 1 和另一个 List 值。这个 List 是另一个包含 2 的 Cons 值和下一个 List 值。接着
又有另一个存放了 3 的 Cons 值和最后一个值为 Nil 的 List,非递归成员代表了列表的结尾。
如果尝试编译示例 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 {
| ^^^^^^^^^ 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),
| ++++ +
error[E0391]: cycle detected when computing drop-check constraints for List
–> src/main.rs:1:1
|
1 | enum List {
| ^^^^^^^^^
|
= note: …which immediately requires computing drop-check constraints for List again
= note: cycle used when computing dropck types for Canonical { max_universe: U0, variables: [], value: ParamEnvAnd { param_env: ParamEnv { caller_bounds: [], reveal: UserFacing }, value: List } }
Some errors have detailed explanations: E0072, E0391.
For more information about an error, try rustc --explain E0072.
error: could not compile cons-list due to 2 previous errors
示例 15-4:尝试定义一个递归枚举时得到的错误
这个错误表明这个类型 ” 有无限的大小”。其原因是 List 的一个成员被定义为是递归的:它直接存放了另
一个相同类型的值。这意味着 Rust 无法计算为了存放 List 值到底需要多少空间。让我们一点一点来看:
首先了解一下 Rust 如何决定需要多少空间来存放一个非递归类型。

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值