熟悉 c++ 的肯定知道 shared_ptr, unique_ptr, 而 Rust 也有智能指针 Box, Rc, Arc, RefCell 等等,本文分享 Box 底层实现
Box<T> 会在堆上分配空间,存储 T 值,并返回对应的指针。同时 Box 也实现了 trait Deref 解引用和 Drop 析构,当 Box 离开作用域时自动释放空间
入门例子 例子来自 the rust book , 为了演示方便,去掉打印语句
1 2 3 fn  main     let  _ = Box ::new(0x11223344 ); } 
将变量 0x11223344 分配在堆上,所谓的装箱,java 同学肯定很熟悉。让我们挂载 docker, 使用 rust-gdb 查看汇编实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Dump of assembler code for function hello_cargo::main:    0x000055555555bdb0  <+0 >:	sub     $0 x18,%rsp    0x000055555555bdb4  <+4 >:	movl   $0 x11223344,0x14 (%rsp) => 0x000055555555bdbc  <+12 >:	mov     $0 x4,%esi    0x000055555555bdc1  <+17 >:	mov     %rsi,%rdi    0x000055555555bdc4  <+20 >:	callq  0x55555555b5b0  <alloc::alloc::exchange_malloc>    0x000055555555bdc9  <+25 >:	mov     %rax,%rcx    0x000055555555bdcc  <+28 >:	mov     %rcx,%rax    0x000055555555bdcf  <+31 >:	movl   $0 x11223344,(%rcx)    0x000055555555bdd5  <+37 >:	mov     %rax,0x8 (%rsp)    0x000055555555bdda  <+42 >:	lea     0x8 (%rsp),%rdi    0x000055555555bddf  <+47 >:	callq  0x55555555bd20  <core::ptr ::drop_in_place<alloc::boxed::Box<i32>>>    0x000055555555bde4  <+52 >:	add     $0 x18,%rsp    0x000055555555bde8  <+56 >:	retq End of assembler dump. 
关键点就两条,alloc::alloc::exchange_malloc 在堆上分配内存空间,然后将 0x11223344 存储到这个 malloc 的地址上
函数结束时,将地址传递给 core::ptr::drop_in_place 去释放,因为编译器知道类型是 alloc::boxed::Box<i32>, 会掉用 Box 相应的 drop 函数
单纯的看这个例子,Box 并不神秘,对应汇编实现,和普通指针没区别,一切约束都是编译期行为
所有权 1 2 3 4 5 fn  main     let  x = Box ::new(String ::from("Rust" ));     let  y = *x;     println! ("x is {}" , x); } 
这个例子中将字符串装箱,其实没必要这么写,因为 String 广义来讲本身就是一种智能指针。这个例子会报错
1 2 3 4 3  |     let  y = *x;  |             -- value  moved here 4  |     println !("x is {}" , x);  |                         ^ value  borrowed here after  move  
*x 解引用后对应 String, 赋值给 y 时执行 move 语义,所有权不在了,所以后续 println 不能打印 x
可以取字符串的不可变引用来 fix
底层实现 1 2 3 4 pub  struct  Box     T: ?Sized ,     #[unstable(feature = "allocator_api" , issue = "32838" )]  A: Allocator = Global, >(Unique<T>, A); 
上面是 Box 的定义,可以看到是一个元组结构体,有两个泛型参数:T 代表任意类型,A 代表内存分配器。标准库里 A 是 Gloal 默认值。其中 T 有一个泛型约束 ?Sized, 表示在编译时可能道理类型大小,也可能不知道
1 2 3 4 5 6 #[stable(feature = "rust1" , since = "1.0.0" )] unsafe  impl <#[may_dangle]  T: ?Sized , A: Allocator> Drop  for  Box <T, A> {    fn  drop mut  self ) {              } } 
这是 Drop 实现,源码里也说了,由编译器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #[stable(feature = "rust1" , since = "1.0.0" )] impl <T: ?Sized , A: Allocator> Deref for  Box <T, A> {    type  Target      fn  deref self ) -> &T {         &**self      } } #[stable(feature = "rust1" , since = "1.0.0" )] impl <T: ?Sized , A: Allocator> DerefMut for  Box <T, A> {    fn  deref_mut mut  self ) -> &mut  T {         &mut  **self      } } 
实现了 Deref 可以定义解引用行为,DerefMut 可变解引用。所以 *x 对应着操作 *(x.deref())
适用场景 官网提到以下三个场景,本质上 Box 和普通指针区别不大,所以用处不如 Rc, Arc, RefCell 广
当类型在编译期不知道大小,但代码场景还要求确认类型大小的时候 
当你有大量数据,需要移动所有权,而不想 copy 数据的时候 
trait 对象,或者称为 dyn 动态分发常用在一个集合中存储不同的类型上,或者参数指定不同的类型 
 
官网提一一个链表的实现
1 2 3 4 enum  List     Cons(i32 , List),     Nil, } 
上面代码是无法运行的,道理也很简单,这是一种递归定义。对应 c 代码也是不行的,我们一般要给 next 类型定义成指针才行
1 2 3 4 5 6 7 8 9 10 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)))))); } 
官网给的解决方案,就是将 next 变成了指针 Box<List>, 算是常识吧,没什么好说的
小结 写文章不容易,如果对大家有所帮助和启发,请大家帮忙点击在看,点赞,分享 三连
关于 Box 大家有什么看法,欢迎留言一起讨论,大牛多留言 ^_^