rust之智能指针总结
前言
最近发现写数据结构需要用智能指针,但是掌握的不好,所以记录下学习的总结。
sized和unsized
对于大部分编译器来说,当把代码编译成二进制格式的时候,需要知道每一个类型的size。但是也有一些类型是没办法确定大小的。unsized类型会被分配到堆上。例如str,String,Vector等。但是编译器在编译struct时候需要确定大小,因为他要分配在栈上。这时候我们需要把他里面的unsize属性,通过指针的方式引用过来。指针类型的大小是确定的。
栈和内存
rust默认是把内存分配在栈上的,除了两个例外:
- value的size是unknown。例如 String和Vector这种动态类型。
- 当使用Box声明初始化一个变量的时候。
智能指针
指针是保存一段内存地址的变量。
智能指针是除了是一个指针同时它会有元数据信息和其他能力。我们将介绍三种智能指针:
- Box<T> 允许数据存储在堆上
- Rc<T> 一个可以引用计数的类型,允许多个所有者
- Ref<T> 和RefMut<T>, 通过RefCell<T>使用,允许在运行时借用。
Box<T>
功能
Box的作用是让我们可以把数据直接存储在内存上而不是栈上。
使用场景
- 数据的size没办法在编译器确定。递归的数据结构类似链表,在编译器的size是没办法确定的,这时候就需要使用Box来存储数据
- 数据很大,如果使用copy的话性能很差,这时候可以直接分配在堆上。
- 获取一个trait的引用时候,我们不关心具体的类型,只想获取实现了某个trait的数据。
使用Box存储数据在堆上
struct Node{
value: i32,
}
fn main() {
let box_node = Box::new(Node { value: 32 });
println!(" {}", box_node.value);
}
使用Box表示递归类型
我们使用Box来实现类似链表的数据结构
struct Node{
value: i32,
next: Box<Option<Node>>
}
fn main() {
let last = Box::new( Some(Node { value: 11, next: Box::new(None) }));
let start = Node {value: 11, next: last};
println!(" {}", start.next.unwrap().value);
}
Rc<T>
在Rust里面value是属于一个变量的。但是在某些情况下,一个value属于多个变量是合理的。例如:图的节点,双链表的节点等。因为没办法在编译器确定所有者,所有者为多个,所以我们需要Rc的帮助。
定义
Rc是Reference counting的缩写。Rc统计有多少个引用存活,当没有引用存活的时候,被引用value就会被释放掉。
ps:Rc只能在单线程中使用,多线程使用的另外的智能指针Arc<T>。
使用Rc实现链表
这个例子是官网,本来想自己写一个来着,失败了...那就用官网的吧
我们来实现两个链表,但是共用后面三个Node。
enum List {
Node(i32, Rc<List>),
Nil,
}
fn main() {
let a = Rc::new(Node(5, Rc::new(Node(10, Rc::new(Nil)))));
let b = Node(3, Rc::clone(&a));
let c = Node(4, Rc::clone(&a));
}
Refcell<T>
Refcell的作用我还是不太明白,但是当我们实现经典数据结构的时候,虽然Rc可以让我们把一个value分配给多个变量,但是Rc的所有者都是只读或者说不可变的。Refcell所有的变量可以改变属性把不可变引用变为可变引用。
另外对于Refcell修饰的变量就不会有编译器的引用检查了。
ps: Refcell也是单线程环境下使用
总结
- Rc\的value可以拥有多个所有者,Box和Refcell只能有一个所有者
- Box对于引用的检查在编译期;Rc在编译期只允许不可变引用。Refcell在运行期检查可变和不可变引用。
- 因为Refcell允许在运行期可变引用value,所以即使是Refcell是不可变的,我们也可以改变value。