一、Box
在Rust语言中,堆和栈的操作一定是离不开的。这玩意儿是老生常谈。许多人,甚至包括一些老鸟,其实都分不清数据结构里的堆和栈与操作系统中的堆和栈有什么不同。这里不对堆和栈进行分析,只要明白堆需要自己控制分配和释放而栈是自动的就可以了。像一些书上讲的啥栈是由高到低的地址顺序,其实这都不严谨,所以要多看一些资料。
自己管理堆内存就意味着可能会有内存泄露,这是所有没有GC的语言都无法回避的问题,也是所有人对指针的一种恐惧,这就是根源。Rust本身会通过生命周期和所有权对类似局部变量引用的返回之类的方式进行编译期的处理,避免了C/C++这类常见的菜鸟式错误。但内存怎么办?老办法,凉拌。
Box来了。Box分配内存有它的一定的缺点,比如可能会占用较多的内存空间。但是凡事,有一利必有一弊,Rust应该是充分考虑过了,才决定上这个Box。从名字就可以知道,把相关的内存分析打个包,这和集装箱有点类似,才不管你内存是啥玩意儿,反正就是一个大箱子,上车,上船,上飞机,都好管理。既然成了箱子,那就出来一个优势,在释放内存时,按同一个套路来就可以了。所以Rust中,正规的套路代码是不用考虑堆上内存的释放的,是不是有点意思。
Box是使用jemalloc来管理内存的,这个和谷歌的tcmalloc不相上下,不过比传统的C/C++中的malloc据说要优秀不少。有兴趣的可以去看看,在Redis等开源框架中,也有类似的代码应用。
二、Box的应用
Box的使用,在前面提到过几次,其实它就是一个智能指针,有点类似于c++的中RAII。在前面的Rust的智能指针中,曾经分析过Rust中智能指针实现的几种机制,而这个Box更类似于上层的近一步封装或者说是智能指针的一个应用类型模板,怎么理解都可以,只要明白了它的机制就可以了。
这回具体的看看,先看代码:
fn main()
{
let b = Box :: new(3);
print!("b is : {}", b);
}
可以通过Box::new来分配内存,在nightly版中提供了box这个关键字来等价其同样可以实现内存的分配。
#![feature(box_syntax, box_patterns)]
fn main() {
let boxed = Some(box 5);
match boxed {
Some(box unboxed) => println!("Some {}", unboxed),
None => println!("None"),
}
}
在Stack Overflow有一个使用Vec 和 Vec<Box>示例(https://stackoverflow.com/questions/21066133/what-is-the-difference-between-veci32-and-vecboxi32/21067103#21067103)。
其实很简单,就是一个直接在一个线性的数组内,一个是在线性的数组内,每个线性内存存储着一个指针指向其它内存。这恰好也解释了Box是一个智能指针,一个装箱的过程。
Box在Rust中有几个主要的用法:
1、Trait对象
trait T {
fn m(&self) -> u64;
}
struct S {
i: u64
}
impl T for S {
fn m(&self) -> u64 { self.i }
}
fn f(x: Box<dyn T>) {
println!("{}", x.m())
}
fn main() {
let s = S{i : 100};
println!("{}", s.m());
let b: Box<S> = Box::new(S{i: 100});
f(b);
}
2、获取类型
这个主要是用在递归里:
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main()
{
let list = Cons(1,Box::new(Cons(2,Box::new(Cons(3,Box::new(Nil))))));
print!("{:?}",list);
}
3、跨线程
在多线程的分析中已经提到过了,“在Rust进行堆的多线程操作,必须使用std::boxed::Box,它又分为可跨线程和不可以跨线程的即std::sync::Arc和std::rc::Rc”,如果有问题可以查看一下相关的技术资料。
- 以上代码主要来自Rust社区和《Rust Primer》
三、总结
Rust总有一种让人说不太清楚的感觉,也可能还是在快速迭代中吧,包括这个Box。至于Rust最后是什么样子,能不能达到它最初的设计的目的,搬个小板凳,看呗。
努力吧,归来的少年!