Rust Learning - Smart Pointers
What is Smart Pointer
Rust 中有&
引用来表示一个指针类型,这也是 C/C++ 等系统级语言所拥有的能力,而 Rust 的智能指针是基于结构体实现的指针类型,主要实现了Deref
(解引用) 和Drop
(释放资源) 特征,而且不同的智能指针还有不同的功能特性。(虽然我觉得有些是在为 Rust 苛责的所有权机制缝缝补补 qwq)
Box
使用 Box<T>
将数据放在堆上,指针结构体放在栈上,是最简单的智能指针,而且没有性能开销。
使用场景:
- 有一个类型但在编译时期不知道大小,但在 context 中又需要这个类型,这种动态类型 DST 并不能直接存在栈上,所以可以使用
Box<T>
仅在栈上存一个指针结构体,然后指向堆上的数据。比如数据结构中的 List,是一个递归的结构,你并不能在编译时知道 List 所需要的大小,用 Rust 的话一种写法就是:
struct List {
val: i32,
next: Box<List>
}
- 数据大,在转移所有权的时候不想让它进行复制数据。比如一个实现了
Copy
特征的i32
的数组的转移问题。 - 以
trait
来描述一个对象而不是用type
,即特征对象。比如考虑一个组件数组,其中有不同组件对象,但都实现了某一特征,那么就可以用一个数组来装入这些特征对象;若用直接用type
的泛型,那么数组中的元素只能是组件的某一个type
。 - 特意把数据存在堆中。
Deref Trait
为了使指针结构体能够和普通的引用类型一样用*
来获取指针指向的值, Rust 实现了 Deref
特征,example:
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
于是你便可以直接对指针结构体使用*a
;编译器就会自动解析得到*(a.deref())
。
use std::ops::Deref;
fn print_type_of<T>(_: &T) {
println!("{}", std::any::type_name::<T>())
}
fn main() {
let a = Box::new(String::from("SE"));
print_type_of(&*a);
print_type_of(&*(a.deref()));
}
除此之外,Deref
还有以下特性:
- 能够进行连续的隐式 Deref 转换
- 赋值操作需要手动解引用
- 方法调用会自动解引用
总结就是:
一个类型为 T
的对象 foo
,如果 T: Deref<Target=U>
,那么,相关 foo
的引用 &foo
在应用的时候会自动转换为 &U
。
除了Deref
,还有一个DerefMut
特征,可以将 &mut T
转换成 &mut U
。
Drop Trait
Rust 为每个类型自动地实现了 Drop
特征,自动帮你在一个变量超出作用域时释放资源。
Drop
的顺序:
- 变量级别,按照逆序的方式
- 结构体内部,按照顺序的方式
其实也就是栈中出栈的过程。
由于它是析构函数,不能显示调用,所以如果想手动触发需要使用drop(T)
函数。
使用场景:
- 回收内存资源
- 执行一些收尾工作
Rc 与 Arc
Rust 的所有权机制让一个值只能拥有一个所有者,但在一些场景下该机制会变得非常棘手,就比如图论中表达点边关系,一个点可能被很多条边拥有,于是就有了 Rc
和 Arc
智能指针来完成这件事,Arc
对于Rc
唯一的区别就是它是原子操作,对于线程是安全的,因此能够用于多线程。
实质上这俩指针干的事就是引用计数 (上个时代的技术了 :< ),可以来个 example:
let a = Rc::new(String::from("hello, world"));
let b = Rc::clone(&a);
assert_eq!(2, Rc::strong_count(&a));
assert_eq!(Rc::strong_count(&a), Rc::strong_count(&b));
然而这样就会带来循环引用的问题,这时就需要用到下面的Weak
智能指针。
Weak
Weak
是一个非常类似于Rc
的弱引用,它不持有数据的所有权,不保证引用关系依然存在,调用upgrade
方法会返回一个Option<Rc<T>>
,因此能够用来解决Rc
带来的弱引用问题。
贴一个树结构的例子:
use std::cell::RefCell;
use std::rc::{Rc, Weak};
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Weak<Node>>,
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let leaf = Rc::new(Node {
value: 3,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![]),
});
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
{
let branch = Rc::new(Node {
value: 5,
parent: RefCell::new(Weak::new()),
children: RefCell::new(vec![Rc::clone(&leaf)]),
});
*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
println!(
"branch strong = {}, weak = {}",
Rc::strong_count(&branch),
Rc::weak_count(&branch),
);
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
}
println!("leaf parent = {:?}", leaf.parent.borrow().upgrade());
println!(
"leaf strong = {}, weak = {}",
Rc::strong_count(&leaf),
Rc::weak_count(&leaf),
);
}
父子关系便可以使用父节点通过Rc
引用子节点,然后子节点通过Weak
来引用父节点。
Cell and RefCell
对于一些另类场景,Rust 的所有权机制完全不可能实现,只能使用 unsafe
代码块进行裸指针操作,或者,使用封装了unsafe
的Cell
和RefCell
智能指针;它们可以在拥有不可变引用的同时修改目标数据,也能对一个不可变的值进行可变借用,即内部可变性。
let c = Cell::new("asdf");
let one = c.get();
c.set("qwer");
let two = c.get();
println!("{},{}", one, two);
Cell
只能针对实现了Copy
特征的类型,对于其他类型需要使用RefCell
。而RefCell
只是将借用规则从编译期推迟到程序运行期,若违反规则,等待你的就不是编译报错,而是通过编译运行时的panic
异常。
RefCell
使用场景:
用于你确信代码是正确的,而编译器却发生了误判时。。。
Pin
Look this.
to be continue...