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 的话一种写法就是:
Copy
struct List { val: i32, next: Box<List> }
  • 数据大,在转移所有权的时候不想让它进行复制数据。比如一个实现了Copy特征的 i32 的数组的转移问题。
  • trait来描述一个对象而不是用type,即特征对象。比如考虑一个组件数组,其中有不同组件对象,但都实现了某一特征,那么就可以用一个数组来装入这些特征对象;若用直接用type的泛型,那么数组中的元素只能是组件的某一个type
  • 特意把数据存在堆中。

Deref Trait#

为了使指针结构体能够和普通的引用类型一样用*来获取指针指向的值, Rust 实现了 Deref 特征,example:

Copy
impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &Self::Target { &self.0 } }

于是你便可以直接对指针结构体使用*a;编译器就会自动解析得到*(a.deref())

Copy
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:

Copy
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带来的弱引用问题。

贴一个树结构的例子:

Copy
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智能指针;它们可以在拥有不可变引用的同时修改目标数据,也能对一个不可变的值进行可变借用,即内部可变性。

Copy
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 @   SilentEAG  阅读(194)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 易语言 —— 开山篇
点击右上角即可分享
微信分享提示
CONTENTS