Rust学习_9.智能指针
智能指针
- 指针:一个变量在内存中包含的是一个地址(指向其他数据);
- Rust里最常见的指针就是引用:
- 使用&
- 借用它指向的值
- 没有其余开销
- 最常见的指针类型
- 智能指针是这样一些数据结构:
- 行为和指针类似;
- 有额外的元数据和功能;
引用计数(reference counting)智能指针类型
- 通过记录所有者的数量,使一份数据被多个所有者同时持有;
- 并在没有任何所有者时自动清理数据。
引用和智能指针的不同:
- 引用:只借用数据;
- 智能指针:很多时候拥有它所指向的数据;
智能指针的例子:
- String 和 Vec
; - 都拥有一片内存区域,且允许用户对其操作;
- 还拥有元数据(例如容量等);
- 提供额外的功能和保障(String 保障其数据是合法的UTF8编码)
智能指针的实现:
- 智能指针通常使用 struct 实现,并且实现了:
- Deref 和 Drop 这两个 trait;
Deref trait: 允许智能指针 struct 的实例像引用一样使用;(可以编写出同时用于引用和智能指针的代码)
Drop trait: 允许自定义当智能指针实例走出作用域时的代码。
1. Box
Box
是最简单的智能指针: - 将数据存储在堆上,并在栈中保留一个指向堆数据的指针;
- 没有性能开销;
- 没有其他额外功能;
- 实现了 Deref trait 和 Drop trait。
Box
的使用场景: - 在编译时,某类型的大小无法确定。但使用该类型时,上下文需要知道它的确切大小。例如:链表结构、树结构;
- 当有大量数据,想移交所有权,但需要确保在操作时数据不会被复制;
- 使用某值时,你只关心它是否实现了特定的trait,而不关心它的具体实现。
2. Deref Trait
- 实现
Deref Trait
使我们可以自定义解引用运算符 * 的行为; - 通过实现
Deref
,智能指针可像常规引用一样来处理。
1 | fn main() { |
MyBox
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22struct MyBox<T> (T);
impl<T> MyBox<T>{
fn new(x:T)->MyBox<T>{
MyBox(x)
}
}
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self)->&T{
&self.0
}
}
fn main() {
let x = 5;
let y = MyBox::new(x);
assert_eq!(5,*y);
}解引用转换
deref coercion
当某个类型T实现了Deref trait时,它能够将T的引用转换为T经过Deref操作后生成的引用。
调用函数和方法时,无需多次显示地使用&和*运算符来进行引用和解引用操作。
1
2
3
4
5
6
7
8fn hello(name:&str){
println!("Hello, {}",name);
}
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m);
}
解引用转换与可变性
- 使用
Deref trait
能够重载不可变引用的*
运算符。 - 执行解引用转换的3中情形:
- 当
T:Deref<Target=U>
时,允许&T
转换为&U
; - 当
T:DerefMut<Target=U>
时,允许&mut T
转换为&mut U
; - 当
T:Deref<Target=U>
时,允许&mut T
转换为&U
。
- 当
- 使用
3. Rc 引用计数智能指针
为了支持多重所有权!通过不可变引用,使你可以在程序不同部分之间共享只读数据。
3.1 Rc使用场景
- 需要在heap上分配数据,这些数据被程序的多个部分读取(只读),但在编译时无法确定哪个部分最后使用完这些数据。
- Rc
只能用于单线程场景。
3.2 注意:
- Rc
不在预导入模块; - Rc::clone(&a) 函数:增加引用计数;
- Rc::strong_count(&a)函数:获得引用计数;
- Rc::weak_count(&a)函数;
3.3 例子
两个List共享另一个List的所有权
1 | enum List{ |
3.4 Rc::clone() VS 类型的clone()方法
- Rc::clone():增加引用,不会执行数据的深度拷贝操作;
- 类型的clone(): 很多都会执行数据的深度拷贝操作;
4. RefCell和内部可变性
4.1 内部可变性(interior mutability)
- 内部可变性是Rust的设计模式之一;
- 允许在持有不可变引用的前提下对数据进行修改
- 数据结构中使用了
unsafe
代码来绕过Rust 正常的可变性和借用规则。
- 数据结构中使用了
4.2 RefCell
- 与 Rc
不同,RefCell 类型代表了其持有数据的唯一所有权。 - 只能用于单线程场景!
4.2.1 RefCell与Box的区别
Box |
RefCell |
---|---|
编译阶段强制代码遵守借用规则 | 运行阶段检查借用规则 |
否则出现错误 | 否则触发 panic |
借用规则:在任何给定时间里,要么只能拥有一个可变引用,要么持有任意数量的不可变引用;
4.2.2 借用规则在不同阶段进行检查的比较
编译阶段 | 运行时 |
---|---|
尽早暴露问题 | 问题暴露延后,甚至到生产环境 |
没有任何运行时开销 | 因借用计数产生些许性能损失 |
对大多数场景是最佳的 | 实现某些特定的内存安全场景(不可变环境中修改自身数据) |
是Rust的默认行为 |
4.2.3 选择Box,Rc,RefCell的依据
Box |
Rc |
RefCell |
|
---|---|---|---|
同一数据的所有者 | 一个 | 多个 | 一个 |
可变性、借用检查 | 可变、不可变借用(编译时检查) | 不可变借用(编译时检查) | 可变、不可变借用(运行时检查) |
4.3 内部可变性案例:
Trait Messenger
中send()
方法接受一个不可变类型的引用,和一个字符串;- 但是,实现以上
Trait
的类MockMessenger
,在实现send()
方法时,需要修改自身属性的值,即需要使一个不可变引用可变。 - 所以,可以将
MockMessenger
中需要修改的属性变为:RefCell<T>
1 | pub trait Messenger{ |
borrow()
:返回内部值得不可变引用“智能指针Ref<T>
”,实现了Deref
;borrow_mut()
:返回内部值得可变引用“智能指针RefMut<T>
”,实现了Deref
;
4.4 RefCell 在运行时记录借用信息
RefCell
会记录当前存在多少个活跃的Ref 和RefMut 智能指针: - 每次调用
borrow()
:不可变借用+1; - 任何一个Ref
的值离开作用域被释放时:不可变借用数-1; - 每次调用
borrow_mut()
:可变借用+1; - 任何一个RefMut
的值离开作用域被释放时:可变借用数-1;
- 每次调用
以此技术来维护借用规则检查:
- 任何一个给定时间里,只允许拥有多个不可变借用或一个可变借用。
4.5 拥有多重所有权的可变数据案例:Rc<T>
+RefCell<T>
RefCell<T>
的一个常见用法是与 Rc<T>
结合。回忆一下 Rc<T>
允许对相同数据有多个所有者,不过只能提供数据的不可变访问。如果有一个储存了 RefCell<T>
的 Rc<T>
的话,就可以得到有多个所有者 并且 可以修改的值了!
1 | use std::rc::Rc; |