rust学习十五.2、智能指针之Box(盒子)指针
Box 原意是盒子,实际生活是作为容器使用。
Rust没有称为Container,而是称为Box,体现了他们一贯的随意性。
不过Box这个词汇倒是可以反映盒子指针的特点:就是一个容器,没有什么特别的。
一、盒子指针定义
#[lang = "owned_box"] #[fundamental] #[stable(feature = "rust1", since = "1.0.0")] #[rustc_insignificant_dtor] // The declaration of the `Box` struct must be kept in sync with the // compiler or ICEs will happen. pub struct Box< T: ?Sized, #[unstable(feature = "allocator_api", issue = "32838")] A: Allocator = Global, >(Unique<T>, A);
其定义倒也满足所谓智能指针要素要求:有元数据,有额外功能
只不过,这个定义,对于初学者而言,颇有难度,因为认识的字母虽多,但是不认识的词汇和语法确居多。
借助有关工具,我们一一看看,这些编译器注解和词汇的含义。
- #[lang = "owned_box"] --指定一个item(通常是结构体或类型别名)作为Rust语言的内置类型.这个属性告诉编译器,被注解的item是语言内置的一部分,用于实现特定的语言特性
-
#[fundamental] 标记一个item是Rust语言基础设施的一部分,通常指的是那些对于Rust运行时和编译器操作至关重要的类型或函数
-
#[stable(feature = "rust1", since = "1.0.0")] -- 标记一个item的稳定性状态
-
#[rustc_insignificant_dtor] -- 标记一个类型的析构函数(drop实现)对于性能或资源管理来说不重要
- ?Sized -- 使用一个不定大小的泛型类型
- Alllocator -- 用于处理内存分配的特质
- Global -- 全局内存分配器,一个实现了Allocator的结构体
- Unique -- 非空数据包装器,也是结构类型
刨除这些编译器注解,那么一个Box主要包含两个成员:数据包装器、全局内存分配器
在标准库boxed.rs中,为Box定义了许多的实现,此处略。
三、盒子指针的应用场景
原书是这么写的:
- 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型值的时候 -- 例如定义递归数据结构
- 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候 -- 这有点拗口
但这个还是让人迷惑?担心在转移所有权的时候,被复制了一份数据? 又转移又拷贝? 暂时没有示例帮助理解这句话。
- 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候。 这个倒是可以理解,因为Box实现了许多特质
但是,其实不用box也能实现这个。
个人觉得,第一个是最重要的,因为其它的,都可以通过其它方式做到替代。
三、协助定义递归数据结构
学到这里,书籍中已经多次提到:Rust 需要在编译时知道类型占用多少空间
换言之,如果无法知道,那么就必须采用曲折的方式让rustc知道这一点。
这同时也意味着,这是一种取巧,并不是真正知道占用多少,只是在某种层面是知道的。
递归数据难点就在于:层次不限,无法知道动态情况下的真正大小。
rust的做法是一种障眼术:把盒子指针作为递归结构的成员(子孙),那么分析顶层结构的时候,总是认为其它大小是固定的,因为一个最简单的递归结构通常有两个项目:数据和子孙。
考虑到子孙是盒子指针,其大小是确定的,故整个递归结构至少在顶层上是能够明确其大小的。
这样说有点绕口,看以下模仿有关书籍编写的例子:
enum RecursiveEnum { Data(i32, Box<RecursiveEnum>), Nil, } struct RecursiveStruct { data: i32, next: Option<Box<RecursiveStruct>>, } use RecursiveEnum::{ Data, Nil }; fn main() { //这两种无论哪一种都是比较怪异的。在实际编程中基本上不可能这样去初始化数据 let list = Data(1, Box::new(Data(2, Box::new(Data(3, Box::new(Nil)))))); let org = RecursiveStruct { data: 1, next: Some( Box::new(RecursiveStruct { data: 2, next: Some( Box::new(RecursiveStruct { data: 3, next: None, }) ), }) ), }; }
以上文的RecursiveStruct为例子:
struct RecursiveStruct { data: i32, next: Option<Box<RecursiveStruct>>, }
因为data是确定大小的,next也是确定的(因为next是基于Box<Recursive>的Option类型)。
说实在,在其它语言中,定义递归结构都很容易,例如java,js等,也不需要特别费脑力。
但rust这里由于其特别的编译原则,书籍的作者不得不大费周章解释为什么。
为什么要这么做,这需要理解一个要点:Rust 需要在编译时知道类型占用多少空间
如何理解?
- 记住这是一个规定。如果不理解请谨记这点。没有太多人会想去研究rustc是如何编写,如何工作的细节。
- 简单和确定的能够带来安全和性能,这个应该都能理解。
- rust内存管理,包括栈内存管理,如果知道大小,自然好管理
- 其它暂时还不需要理解的编译器内核
四、 盒子指针试用
定义递归的示例见前文,此处略。
let cn=String::from("中国"); let cn_box=Box::new(cn); println!("length of {} is {}",cn_box,cn_box.len()); let pcn=*cn_box; println!("length of {} is {}",pcn,pcn.len()); //let pcn2=*cn_box; 这里会报错,因为 *Box 返回的是Box值的所有权,而不是引用
以上例子,有许多非Box所特有的概念才能解释的,此处略,只能提一下:Deref,Drop, 引用隐式转换
尤其是Deref,Drop两个特质,有了它们,Box才有更大的作用。
五、小结
- 盒子指针是比较简单的一种智能指针
- 它让人印象深刻的地方在于:可以辅助定义递归数据
- 它有多个应用场景,但只有递归让我印象深刻
- 它的功能绝对不限于辅助定义递归,否则boxes.rs不需要写那么多
- 通过理解 Box的定义,能够大体明白更加复杂的智能指针是什么样的
- 光通过本文的内容,还无法完全理解盒子指针。所以后面还会有文章阐述Deref,Drop等特质
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek-R1本地部署如何选择适合你的版本?看这里
· 传国玉玺易主,ai.com竟然跳转到国产AI
· 自己如何在本地电脑从零搭建DeepSeek!手把手教学,快来看看! (建议收藏)
· 我们是如何解决abp身上的几个痛点
· 普通人也能轻松掌握的20个DeepSeek高频提示词(2025版)