RUST Smart Pointers
Rust 程序设计语言 - Rust 程序设计语言 简体中文版 (kaisery.github.io)
智能指针
rust中的智能指针和C++的一样,它包了一个指针同时带了一起基本功能和属性,例如引用计数。其实String
和Vec<T>
也是智能指针,因为他们也拥有一块可以操作的内存。
引用Borrow它指向的数据,引用不能改变所有权。智能指针拥有它指向的数据
智能指针也使用struct来实现,只是会实现Deref
和Drop
两个traits。
Box
Box<T>
指向堆上的数据的智能指针。使用Box<T>
有三种场景
- 编译期无法获取数据大小的数据类型
- 大块的数据转移所有权,但又不想拷贝这些数据,提高性能
- 拥有一个数据时,只关心它实现的traits而不是具体的什么类型
Cons List
cons list是来源于Lisp语言的链表数据结构,这个链表中有两个元素,第一个元素是数据,第二个是下一个链表的元素。这个名字来源于cons function(construct function)
在Lisp使用两个参数构造(cons)一对值(pair),这两个参数又分别是值和另一个pair。
例如(1, (2, (3, Nil)))
就是有三个元素的链表。linux中的struct list
其实和这个一样,都是在list的结构中包含了下一个list的元素。
例如定义一个链表枚举
1 | enum List { |
当定义一个let list = Cons(1, Cons(2, Cons(3, Nil)));
这样的链表时,由于链表中元素的第二个成员是另一个list,而下一个list里面又包含了一个list,编译器无法推导出这个list变量到底占用多少空间,会提示错误。此时可以将第二个成员改为Box类型,把数据放在堆上,因为Box的大小是固定的,所以编译器就可以推导出list变量占用大小。
1 | enum List { |
Deref Trait
Deref
定义了智能指针解引用的行为。一个常规的引用类型可以看作指向存储在某个地方的值的指针。我们可以使用*
来获取引用指向的值。使用Box<T>
可以达到和引用相同的效果
1 | fn main() { |
自定义deref
对于自定义类型,可以通过实现Deref
让rust使用*
解引用一个数据。rust会把*y
替换为*(y.deref())
,这里的*
替换只会工作一次,而不会把替换后的*
再次进行替换。
1 | use std::ops::Deref; |
函数和方法中的隐式解引用规则
Deref coerciont特性可以把一个实现了Deref
trait的引用类型转换为另一个类型的引用。例如把一个&String
类型的参数值传递给一个需要&str
的函数,因为&String
的Deref
返回一个&str
,所以这种调用就是可行的。这样函数和方法中的传入参数就不需要明确写*
或&
.当一个类型实现了Deref
trait,rust编译器会调用调用尽可能多次的Deref::deref
来让传入的参数引用去匹配函数需要的参数类型,这个执行过程在编译期完成,所以不会有性能影响。
1 | fn hello(name: &str) {// 以&str为参数的函数 |
可变引用的解引用
使用DerefMut
trait来实现mutable引用的解引用
基本规则
- 当T实现了
Deref
trait返回&U
类型,那么编译器会把&T
转变为&U
- 当T实现了
DerefMut
trait返回&mut U
类型,那么编译器会把&mut T
转变为&mut U
- 当T只实现了
Deref
trait返回&U
类型,那么编译器会把&mut T
转变为&U
Drop Trait
当一个变量执行出它的作用域后,会执行这个类型的Drop
trait。例如Box<T>
类型的变量越过它的作用域后,就会释放堆上的数据。
1 | struct CustomSmartPointer { |
在main函数执行结束时,会先输出变量d的Drop,再输出变量c的Drop。
强制调用Drop
有时需要在出作用域之前提前释放资源,就需要提前执行drop,例如多线程使用的lock,需要在函数执行结束前就释放。但是rust不支持显式调用drop,主要为了避免多次释放资源,此时需要使用std::mem::drop
函数。
1 | let c = CustomSmartPointer { |
Rc
Rc是引用计数的缩写,用来处理一个对象有多个使用者的场景,当一个引用者退出生命周期,引用计数会减少1。它只能在单线程中使用。
通过使用Rc::new
来创建一个Rc<T>
的类型,使用Rc::clone(&a)
的方式来增加a的引用计数,而不是使用a.clone()
,这是为了让程序代码更可读,直接可以看出来是引用计数的浅拷贝,而不是clone的深拷贝。
1 | enum List { |
RefCell
有些时候,编译器的编译期无法判断程序代码是否正确的满足了借用规则,但是如果严格写满足编译规则的代码,编程又会不方便,所以rust允许开发人员在自己保证借用规则正确的前提下,有一些unsafe的代码。
Interior mutability*内部可变性是rust的一种设计模式,它允许修改一个不可变引用内部的数据。例如一个trait参数是不可变引用,但是在一些特殊场景又需要修改这个参数的内部数据,例如单元测试时修改用于测试的假数据。
RefCell
Box
Rc
RefCell<T>
的 borrow
方法返回 Ref<T>
不可变智能指针,borrow_mut
返回可变的智能指针RefMut<T>
. RefCell<T>
会记录当前有多少个 Ref<T>
和 RefMut<T>
的智能指针,从而保证可以有多个不可变指针和一个可变指针,这个检查在运行时判断,如果不满足引用规则,就会产生panic。 RefCell<T>
只能在一个线程中使用,Mutex<T>
是它的多线程版本。
例如在一个作用域内创建两个可变可变智能指针程序在编译时不会出错,但是运行时就会报错。使用 RefCell<T>
可能会把错误漏出到程序的生产环境中,而不是在编译期提前发现同时还增加了运行时的负担,但是能增加程序实现的灵活性。
1 |
|