Skip to content

Rust 智能指针

· 6 min

在 C 语言里,指针的概念就是存储地址的变量。

而 rust 智能指针就是在指针的基础上添加了一些功能。

Box<T>#

允许将数据存储在堆而不是栈上,然后在栈上保留一个指针指向堆上的数据。

  1. 特意的将数据分配在堆上
// 此时 1 分配在堆上
let n = Box::new(1);
  1. 当栈上数据较大时,又不想在转移所有权时进行数据拷贝
let arr = [0;1000];
// 因为 arr 分配到了栈上,此处发生了深复制
let arr2 = arr;
let arr = Box::new([0;1000]);
// 没有深复制,发生了所有权转移,arr 不能在访问
let arr2 = arr;
  1. 将动态大小类型变为 Sized 固定大小类型
enum List {
Cons(i32, List),
Nil,
}

以上代码,编译时会报错,因为 Rust 需要在编译时知道类型占用多少空间,而 List 的 Cons 可以无限嵌套。

enum List {
Cons(i32, Box<List>),
Nil,
}

因为 Box 只包含一个指针指向数据和一个指针指向类型,是已知大小的,所以通过。

TODO

  1. 特征对象

实现不同类型组成的数组

trait Draw {
fn draw(&self);
}
struct Button {
id: u32,
}
impl Draw for Button {
fn draw(&self) {
println!("这是屏幕上第{}号按钮", self.id)
}
}
struct Select {
id: u32,
}
impl Draw for Select {
fn draw(&self) {
println!("这个选择框贼难用{}", self.id)
}
}
fn main() {
let elems: Vec<Box<dyn Draw>> = vec![Box::new(Button { id: 1 }), Box::new(Select { id: 2 })];
for e in elems {
e.draw()
}
}

Box::leak

消费掉 Box 并且强制目标值从内存中泄漏(变成全局的值)

TODO 待验证

Box::from_raw 可以收回来

Rc<T> 引用计数(reference counting)#

由于 rust 所有权规则,变量只能有一个所有者,而 Rc 可以使变量有多个所有者。

Rc 通过克隆 RcBox 结构体的指针来实现多个所有者。而 RcBox 包含以下字段:

#[repr(C)]
struct RcBox<T: ?Sized> {
strong: Cell<usize>, // value 强引用所有者的个数,当为零时清除变量
weak: Cell<usize>, // value 弱引用所有者的个数,不影响变量的清除
value: T,
}
let rc = Rc::new("");
// 获取强引用计数
Rc::strong_count(&rc);
// 获取弱引用计数
Rc::weak_count(&rc);

Weak<T>#

let rc = Rc::new("a")
let weak = Rc::downgrade(&rc)

通过 Rc::downgrade 来生成值的弱应用

Arc (atomic reference counting)#

Rc 的升级版,支持多线程,因为 Rc 需要管理引用计数,但是该计数器并没有使用任何并发原语,因此无法实现原子化的计数操作,最终会导致计数错误。

Cell<T>#

提供内部可变性的容器(没有额外的性能损耗),不指定 mut 也可以实现修改变量

let c = Cell::new("a");
let value = c.get();
// 改变了 c 内部的值
c.set("b")

RefCell<T>#

同 Cell,但是 T 不需要实现 Clone

Cow 写时复制(Copy on Write)#

适用于读多写少的场景。先借用变量进行读操作,当进行写操作时,先对变量进行复制操作后,再进行写操作。

pub enum Cow<'a, B>
Where
B: 'a + ToOwned + 'a + ?Sized
{
Borrowed(&'a + B), // 引用
Owned(<B as ToOwned>::Owned) // 所有者
}
// 返回变量的可变引用。当为引用无所有权时,先进行复制后再返回可变引用
fn to_mut(&mut self) -> &mut <B as ToOwned>::Owned;
// 转移所有权。当为引用,无所有权时,先进行复制后再返回
fn into_owned(self) -> <B as ToOwned>::Owned;

Deref DerefMut#

Deref#

struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> Deref for MyBox<T> {
type Target = T; // 关联类型
fn deref(&self) -> &T {
&self.0
}
}
fn deref_test() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y); // *y 会被 Rust 隐式地展开为 *(y.deref())
}

当我们将某个特定类型的值引用作为参数传递给函数,但是传入的类型与参数类型不一致时,会自动发生解引用转换

fn hello(name: &str) {
println!("Hello, {}", name);
}
fn deref_test() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
}

这里将 MyBox<String> 值的引用传入 hello 函数。因为 MyBox 实现了 Deref,所以 Rust 通过 deref 来将 &MyBox<String> 转换为 &String,而标准库的 String 实现了 Deref,所以 Rust 可以继续调用 deref 来将 &String 转换为 &str,并最终与 hello 函数的定义相匹配。

DerefMut#

Drop#

允许我们在变量离开作用域的时候执行某些自定义操作。

参考资料#

智能指针

Box<T> 堆对象分配

Rc 与 Arc

引用循环与内存泄漏

Rust源码阅读: Cell/RefCell与内部可变性