智能指针(smart pointers)是一类数据结构,他们的表现类似指针,但是也拥有 额外的元数据和功能s
智能指针通常使用结构体实现。
智能指针区别于常规结构体的显著特性在于其实现了 Deref
和 Drop
trait。
- Deref trait 允许智能指针结构体实例表现的像引用一样,这样就可以编写既 用于引用、又用于智能指针的代码。
- Drop trait 允许我们自定义当智能指针离开作用域时运行 的代码。
内容
Box<T>
,用于在堆上分配值Rc<T>
,一个引用计数类型,其数据可以有多个所有者Ref<T>
和RefMut<T>
,通过RefCell<T>
访问,一个在运行时而不是在编译时执行借用规则的类型。
内部可变性(interior mutability)模式,这时不可变类型暴露出改变其内部值的 API。
引用循环(reference cycles)会如何泄露内存,以及如何避免。
使用 Box<T>
指向堆上的数据
- 当有一个在编译时未知大小的类型,而又想要在需要确切大小的上下文中使用这个类型 值的时候
- 当有大量数据并希望在确保数据不被拷贝的情况下转移所有权的时候
- 当希望拥有一个值并只关心它的类型是否实现了特定 trait 而不是其具体类型的时候
通过 Deref trait 将智能指针当作常规引用处理
实现 Deref trait 允许我们重载 解引用运算符(dereference operator) *
。通过这种方式实现 Deref trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针。
通过解引用运算符追踪指针的值
fn main() {
let i = Box::new(10);
// Option
println!("i = {:?}", i);
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
}
像引用一样使用 Box<T>
fn main() {
let x = 5;
let y = Box::new(5);
assert_eq!(5, x);
assert_eq!(5, *y);
}
自定义智能指针
use std::ops::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 {
println!("deref");
&self.0
}
}
fn main() {
let x = 5;
// let y = Box::new(5);
let y = MyBox::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
let z = *(y.deref());
println!("z = {:?}", z);
}
Drop Trait
值要离开作用域时执行一 些代码
Drop Trait的意义在于自动处理,不能手动调用Drop Trait的drop方法. 如果需要提前释放,调用std::mem::drop
函数和方法的隐式解引用强制多态
解引用强制多态(deref coercions)是 Rust 表现在函数或方法传参上的一种便利, 传参时解引用去匹配类型
Deref, DerefMut重载可变或不可变
Rust 在发现类型和 trait 实现满足三种情况时会进行解引用强制多态:
- 当
T: Deref<Target=U>
时从&T
到 &U 。 - 当
T: DerefMut<Target=U>
时从&mut T
到&mut U
。 - 当
T: Deref<Target=U>
时从&mut T
到&U
。
Rc<T>
引用计数智能指针
Rc:reference counting缩写
引用计数意味着记录一个值引用的数量来知晓这个值是否仍在被使用。如 果某个值有零个引用,就代表没有任何有效引用并可以被清理。
只能用于单线程场景
怎么增加计数:Rc::clone / var.clone()
如何查看计数:Rc::strong_count(&a);
问题:相同位置的多个可变借用可能造成数 据竞争和不一致
RefCell<T>
和内部可变性模式
允许你即使在有不可变引用 时改变数据; 不可变值的可变借用
RefCell<T>
代表其数据的唯一的所有权
RefCell<T>
正是用于当你确信代码遵守借用规则,而编译器不能理解和确定的 时候。
RefCell<T>
只能用于单线程场景
检查借用规则
有些分析是不可能的,
编译器 程序 程序员
如下为选择 Box<T>
, Rc<T>
或 RefCell<T>
的理由:
Rc<T
> 允许相同数据有多个所有者;Box<T>
和RefCell<T>
有单一所有者。Box<T>
允许在编译时执行不可变或可变借用检查;Rc<T>
仅允许在编译时执行不可变 借用检查;RefCell<T>
允许在运行时执行不可变或可变借用检查。- 因为
RefCell<T>
允许在运行时执行可变借用检查,所以我们可以在即便RefCell<T>
自身是不可变的情况下修改其内部的值。
对于引用和 Box<T>
,借用规则的不可变性作用于编译时。对于 RefCell<T>
,这些不可变性 作用于 运行时。
标准库中其他提供内部可变性的类型
Cell<T>
:类似(RefCell<T>
)除了 相比提供内部值的引用,其值被拷贝进和拷贝出Cell<T>
Mutex<T>
:提供线程间 安全的内部可变性
引用循环与内存泄漏