- 智能指针(smart pointers)是一类数据结构,他们的表现类似指针,但是也拥有额外的元数据和功能
- 引用是一类只借用数据的指针;相反,在大部分情况下,智能指针拥有他们指向的数据
16.1 Box<T>:指向堆上的数据
box
允许将一个值放在堆上而不是栈上,box
是一个本身留在栈上但指向堆数据的指针box
的作用:提供固定大小、提供堆分配、间接存储
16.1.1 使用 Box<T> 在堆上储存数据
box
:在离开作用域时,将被释放,释放过程作用于box
本身(位于栈上)和它所指向的数据(位于堆上)
16.1.2 Box 的应用:创建递归类型
16.1.3 计算非递归类型的大小
enum
:实际上只会使用其中的一个成员,所以枚举值所需的空间等于储存其最大成员的空间大小
16.1.4 使用 Box<T> 给递归类型一个已知的大小
Box<T>
是一个指针,我们总是知道它需要多少空间:指针的大小并不会根据其指向的数据量而改变- 核心思想:建议中的 “indirection” 意味着不同于直接储存一个值,应该间接的储存一个指向值的指针!
- 通过使用
box
,打破了这无限递归的连锁,这样编译器就能够计算出储存 List 值需要的大小了
16.2 通过 Deref trait 将智能指针当作常规引用处理
- 实现
Deref
trait 的智能指针可以被当作常规引用来对待,可以编写操作引用的代码并用于智能指针
16.2.1 解引用 *:追踪指针的值
- 解引用运算符
*
可用于追踪引用&
所指向的值
16.2.2 像引用一样使用 Box<T>
Box<T>
可用于生成拷贝一个值的引用(注意是引用,而不是值)的实例(而不是指向该值的引用,因为生成的是Box
引用,不是常规引用,二者不可比)
16.2.3 自定义智能指针(一):引入
16.2.4 自定义智能指针(二):实现 Deref trait 将某类型像引用一样处理
- 对于实现
Deref
trait 的数据类型,要求实现deref
方法,该方法借用self
并返回一个内部数据的引用、并可用于后续的解引用 - 没有
Deref
trait 的话,编译器只会解引用&
引用类型 *y
的底层运行为*(y.deref())
:Rust 将*
运算符替换为先调用deref
方法再进行普通解引用的操作
16.2.5 函数和方法的隐式 Deref 强制转换
- Deref 强制转换(deref coercions)将实现了 Deref trait 的类型的引用转换为另一种类型的引用
- 当这种特定类型的引用作为实参传递给和形参类型不同的函数或方法时将自动进行,这时会有一系列的 deref 方法被调用,把我们提供的类型转换成了参数所需的类型
16.2.6 Deref 强制转换如何与可变性交互
Deref
trait 重载不可变引用的*
运算符;DerefMut
trait 用于重载可变引用的 * 运算符- Rust 在发现类型和 trait 实现满足三种情况时会进行 Deref 强制转换,如下所示:
-
- 当
T: Deref<Target=U>
时从&T
到&U
:如果有一个&T
,而T
实现了返回U
类型的 Deref,则可以直接得到&U
- 当
-
- 当
T: DerefMut<Target=U>
时从&mut T
到&mut U
:与 1 类似(同上)
- 当
-
- 当
T: Deref<Target=U>
时从&mut T
到&U
:Rust 也会将可变引用强转为不可变引用,但与之相反的,不可变引用永远也不能强转为可变引用!
- 当
16.3 Drop Trait:运行清理代码
16.3.1 Drop Trait 的基本概念
16.3.2 std::mem::drop:提前丢弃值
std::mem::drop
可显式调用来丢弃值,该方法已经存在于 prelude 中- 所有权系统确保引用总是有效的,也会确保
drop
只会在值不再被使用时被调用一次
16.4 Rc<T>:引用计数智能指针
- 引用计数(reference counting):意味着记录一个值引用的数量来知晓这个值是否仍在被使用,如果某个值有零个引用,就代表没有任何有效引用并可以被清理
- 如果确实知道哪部分是最后一个结束使用的话,就可以令其成为数据的所有者,正常的所有权规则就可以在编译时生效
- 为了启用多所有权需要显式地使用 Rust 类型
Rc<T>
,注意Rc<T>
只能用于单线程场景
16.4.1 使用 Rc<T> 共享数据
Rc<T>
可通过克隆的方式Rc::clone
来增加引用计数,直到有零个引用之前其数据都不会被清理Rc::clone
的实现并不像大部分类型的clone
实现那样对所有数据进行深拷贝,只会增加引用计数,这并不会花费多少时间,可以明显的区别深拷贝类的克隆和增加引用计数类的克隆(在下面的示例中也可以调用a.clone()
)
16.4.2 克隆 Rc 会增加引用计数
Rc::strong_count
:返回引用计数的值
16.5 RefCell<T> 和内部可变性模式
- 内部可变性(Interior mutability)是 Rust 中的一个设计模式,它允许即使在有不可变引用时也可以改变数据,这通常是借用规则所不允许的。为了改变数据,该模式在数据结构中使用
unsafe
代码来模糊 Rust 通常的可变性和借用规则
16.5.1 RefCell<T>:在运行时检查借用规则
- 对于引用和
Box<T>
:借用规则的不可变性作用于编译时;如果违反这些规则会得到一个编译错误 - 对于
RefCell<T>
:这些不可变性作用于运行时;而对于RefCell<T>
,如果违反这些规则程序会panic
并退出
16.5.2 内部可变性:不可变值的可变借用
- 借用规则推论:对于一个不可变值,不能可变的借用它
16.5.3 内部可变性的示例:mock 对象
std::cell::RefCell
:内部可变性相关模块- 在下面的示例中,对于要修改的结构体字段
sent_messages
字段的类型是RefCell<Vec<String>>
而不是Vec<String>
;在 new 函数中新建了一个RefCell<Vec<String>>
实例替代空vector
RefCell::borrow_mut
方法来获取 RefCell 中值的可变引用;RefCell::borrow
方法来获取 RefCell 中值的不可变引用
16.5.4 RefCell<T>:在运行时记录借用
- 不可变引用:一般使用
&
语法;对于RefCell<T>
使用borrow
方法,返回一个Ref<T>
类型的智能指针,且实现了Deref
trait - 可变引用:一般使用
&mut
语法;对于RefCell<T>
使用borrow_mut
方法,返回一个RefMut<T>
类型的智能指针,且实现了Deref
trait RefCell<T>
记录当前有多少个活动的Ref<T>
和RefMut<T>
智能指针- 每次调用
borrow
,RefCell<T>
将活动的不可变借用计数加一;当Ref<T>
值离开作用域时,不可变借用计数减一 - 像编译时借用规则一样,
RefCell<T>
在任何时候只允许有多个不可变借用或一个可变借用 - 如果违反借用规则,相比引用时的编译时错误,
RefCell<T>
的实现会在运行时出现panic
16.5.5 结合 Rc<T> 和 RefCell<T> 来拥有多个可变数据所有者
16.6 引用循环与内存泄漏
16.6.1 制造引用循环
- 创建引用循环并不容易,但也不是不可能:如果你有包含
Rc<T>
的RefCell<T>
值或类似的嵌套结合了内部可变性和引用计数的类型,请小心检查有没有形成一个引用循环 - 另一个解决方案是重新组织数据结构,使得一部分引用拥有所有权而另一部分没有(换句话说,循环将由一些拥有所有权的关系和一些无所有权的关系组成,只有所有权关系才能影响值是否可以被丢弃)
- 代码解析:
*link.borrow_mut() = Rc::clone(&b);
表示将取出的变量link
引用通过borrow_mut()
方法来获得其可变引用,再用*
来解引用指针,解引用后将这个可变引用重新指向 b 的克隆引用(*link.borrow_mut()
应该等价于*(link.borrow_mut())
)
16.6.2 避免引用循环:将 Rc<T> 变为 Weak<T>
- 创建弱引用(weak reference):调用
Rc::downgrade
并传递Rc<T>
实例的引用来创建其值的弱引用,并会得到Weak<T>
类型的智能指针,会将weak_count
加 1。创建弱引用的例子:*leaf.parent.borrow_mut() = Rc::downgrade(&branch);
- 弱引用的
weak_count
:用来记录存在多少个Weak<T>
引用,与strong_count
的区别在于,weak_count
无需计数为 0 就能使Rc<T>
实例被清理 - 使用
upgrade
方法用于判断指向的值是否被丢弃:因为Weak<T>
引用的值可能已经被丢弃了,为了使用Weak<T>
所指向的值,我们必须确保其值仍然有效,为此可以调用Weak<T>
实例的upgrade
方法,这会返回Option<Rc<T>>
- 强、若引用的区别:强引用代表如何共享
Rc<T>
实例的所有权(引用并拥有),但弱引用并不属于所有权关系(仅引用,但不拥有)! - 弱引用不会造成引用循环,因为任何弱引用的循环会在其相关的强引用计数为 0 时被打断