5分钟速读之Rust权威指南(二十四)Box

360 阅读2分钟

从本节开始介绍智能指针(smart pointer),智能指针是一些数据结构,它们的行为类似于指针,但拥有额外的元数据和附加功能,引用和智能指针不同,引用是只借用数据的指针;而大多数的智能指针本身就拥有它们指向的数据,也就是具有数据的所有权。

Box

装箱类型(box)使我们可以将数据存储在堆上,并在栈中保留一个指向堆数据的指针。

使用场景

  • 当拥有一个无法在编译时确定大小的类型,但又想要在一个要求固定尺寸的上下文环境中使用这个类型的值时。
  • 当需要传递大量数据的所有权,但又不希望产生大量数据的复制行为时。
  • 当希望拥有一个实现了指定trait的类型值,但又不关心具体的类型时。

使用装箱

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

另外,和其他任何拥有所有权的值一样,装箱会在离开自己的作用域时被释放,包括指针和堆上的数据。

使用装箱定义递归类型

rust中使用基本类型是无法实现递归的,当然你也可以自行试一下实现一个链表数据结构,过程中必然会得到rust的报错,因为rust无法确定递归类型的大小,进而无法通过编译。

使用枚举实现链表结构

下面我们使用rust尝试实现链表,看一下rust的报错:

#[derive(Debug)]
enum List {
  Cons(i32, List), // error, 递归类型有无限的大小
  Nil,
}
let list = List::Cons(1,
  List::Cons(2,
    List::Cons(3, List::Nil)
  )
);
println!("{:?}", list);

由于rust在编译过程中需要知道所有类型的大小,而在运行时递归是无限的,所以在编译过程中无法计算出递归的大小

使用Box将递归类型的大小固定下来

下面使用Box类型重新尝试看看如何实现链表:

#[derive(Debug)]
enum List {
  Node(i32, Box<List>), // 使用Box标记类型
  Nil,
}
let list = List::Node(1,
  Box::new(List::Node(2,
    Box::new(List::Node(3,
      Box::new(List::Nil))
    )
  ))
);
println!("{:?}", list);
// Node(1, Node(2, Node(3, Nil)))

因为Box是一个指针,所以Rust可以在编译时就确定一个Box的具体大小。指针的大小总是恒定的,它不会因为指向数据的大小而产生变化。

结构体实现链表结构

上面使用的枚举来实现链表,我们也可以使用结构体来实现:

#[derive(Debug)]
struct Node<T> {
  value: T,
  next: Box<Option<Node<T>>>
}
let list = Node {
  value: 1,
  next: Box::new(Some(Node {
    value: 1,
    next: Box::new(None)
  }))
};
println!("{:?}", list);
// Node { value: 1, next: Some(Node { value: 1, next: None }) }

Box类型比较简单,所以本节内容不多,后面会介绍智能指针相关的trait,和更多智能指针的类型。