The Rust Programming Language - 第15章 智能指针 - 15.1 使用Box<T>指向堆上的数据

本文介绍了Rust中的智能指针,特别是Box类型,它用于在堆上分配数据并实现了Deref trait。Box在三种场景下尤其有用:不确定变量大小、大量数据存储和创建递归类型。通过Box,可以解决递归类型在编译时大小无法确定的问题,因为它有一个固定的大小。文中还展示了如何使用Box创建和操作递归的cons list类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

15 智能指针

指针指向变量的内存地址,除了引用数据没有其它的功能,因此没有运行开销

智能指针是一类数据结构,虽然表现类似指针,但是拥有额外的元数据和功能。Rust的智能指针提供了包含引用之外的其他功能,但是指针这个概念并不是Rust独有的

在Rust中,普通指针只是借用数据,而智能指针还拥有它们指向的数据,比如String和Vec,它们都是智者指针,它们拥有数据并且可以被修改。它们也带有元数据(比如容量)和额外的功能和保证(String的数据总是有效的UTF-8编码)

智能指针通常使用结构体来实现,区别与常规结构体的是它们实现了deref 和 drop trait。deref trait允许智能指针结构体实例表现的像引用一样,这样可以编写既可以用于引用又可以用于智能指针的代码。Drop trait 允许我们自定义智能指针离开作用域时运行的代码

智能指针在Rust中是一个通用设计模式,很多库都有智能指针并且你也可以编写自己的智能指针,本章我们将会学习以下:

Box,用于在堆上分配值,实现了Deref trait值,允许Box被当作引用对待,当Box离开作用域时,由于Box类型Drop trait的实现,box所指向的数据也会被清除

Rc,一个引用计数类型,其数据可以有多个所有者

Ref和RefMut,通过RefCell访问(RefCell是一个在运行时而不是在编译时执行借用规则的类型)

我们还会涉及内部可变性模式,这是不可变类型暴露出改变其内部值的API,我们也会讨论引用循环会如何泄露内存,以及如何避免

15.1 使用Box指向堆上的数据

box是最简单直接的智能指针,它的类型是box,它允许我们将数据存放在堆上,而将数据的指针存档在堆上box有三个典型的应用场景:

1.你要定义一个变量,但是不知道它的大小,但是你又需要在上下文中需要知道它的确切大小并且使用它

2.当你想保存大量的数据,但是同时又不想拷贝他们的时候

3.你只关心某个值的类型是否实现了某个trait而不关系这个值的具体类型

我们接下来针对这三种情况一一介绍

使用Box在堆上存储数据

我们在堆上存放一个 i32数据

fn main() {
    let b = Box::new(5);
    println!("b = {}",b);
}

这里定义的变量b,它的值指的是值为5的Box,而这个5分配在堆上而不是栈上。我们访问box类型的值是就像访问存储在栈上的数据一样。box在main的末尾离开作用域,它将被释放。这个释放过程针对于box的本身()位于栈上和它所指的数据(位于堆上),但是呢,这里只是为了演示,不然,将一个单独的值存放在堆上并没有太大的意义

Box允许创建递归类型

Rust在编译时需要知道类型占用多少内存空间,但是递归类型的数据在编译时是无法确定的。但是Box大小已知,所以在循环结构中创建box,就可以实现创建递归类型

我们来看一个关于cons list类型的例子

cons list 的更多内容

cons list来源于被Lisp编程语言及其方言的数据结构。在Lisp中,cons函数利用两个参数构建新的列表,它们通常是一个单独的值和另一个列表

cons list的每一项都包含两个元素:当前项的值和下一项。最后一个项值包含一个叫做Nil的值且没有下一项。cons list通过递归调用cons函数产生,代表递归的终止条件的规范名称是Nil,它宣布列表的终止

虽然函数式编程语言经常使用cons list,但是Rust中它并不常见,所以在创建列表的时候,Vec是更好的选择

但是这里我们使用cons list作为例子,建一个cons list的枚举定义,不能编译是因为该类型大小未知

enum List {
    Cons(i32,List),
    Nil,
}

我们现在来存放1,2 ,3

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

我们来编译一下看看有什么问题

error[E0072]: recursive type `List` has infinite size
 --> src/main.rs:5:1
  |
5 | enum List {
  | ^^^^^^^^^ recursive type has infinite size
6 |     Cons(i32,List),
  |              ---- recursive without indirection
  |
help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable
  |
6 |     Cons(i32,Box<List>),
  |              ^^^^    ^

错误提示我们List类型有“无限大小”,因为List的一个成员是递归的,它直接存放了另一个相同类型的值,Rust无法计算为了存放List需要多少空间

计算非递归类型的大小

让我们先看看,Rust是如何决定需要多少空间来存放一个非递归类型

enum Message {
    Quit,
    Move{x:i32,y:i32},
    Write(String),
    ChangeColor(i32,i32,i32)
}

在这个枚举中,rust会检查枚举Message的每一个成员,比如发现Message::Quit不需要任何空间,Message::Move需要两个i32值的空间,以此类推,并且我们知道,Message每次只会使用其中一个成员,所以M essage值所需的空间等于存储其最大成员的空间大小

但是由于rust决定变量的存储空间是按照上述逻辑,所以对于List这种递归类型,这种空间计算会永远计算下去

使用Box给递归类型一个已知的大小

我们先来看一下上述错误的帮助提示

help: insert some indirection (e.g., a `Box`, `Rc`, or `&`) to make `List` representable

因为Box是一个指针,它的空间是可计算的。我们来使用Box来改变一下

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))))));
}

现在Rust就可以计算出cons成员的所需空间了:即i32的大小加上存储box指针的数据空间

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值