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 的所有权机制让一个值只能拥有一个所有者,但在一些场景下该机制会变得非常棘手,就比如图论中表达点边关系,一个点可能被很多条边拥有,于是就有了 RcArc 智能指针来完成这件事,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 代码块进行裸指针操作,或者,使用封装了unsafeCellRefCell智能指针;它们可以在拥有不可变引用的同时修改目标数据,也能对一个不可变的值进行可变借用,即内部可变性。

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...

posted @ 2022-07-12 22:17  SilentEAG  阅读(188)  评论(1编辑  收藏  举报