GhostCell 是另一个内部可变性的实现。相比 RefCell,它实现的是编译期的检查。
问题
Rust 中面临所谓 AXM 的问题。A 指的是 Aliasing,M 指的是 Mutability。AXM 表示 A Xor M,也就是这两个只能保证其一。
这让 Rust 容易实现树状结构,而难以实现图状结构(比如双向链表、图等)。其原因是图状结构中有环,环意味着 internal sharing。
对此,Rust 一般提供两种做法:
- raw 指针
具有下面的特点:- Unsafe,也就是 Rust 的类型系统无法进行约束
- 没有 AXM 限制
- Interior Mutability
- 非线程安全的有 Cell 和 RefCell,它们允许通过一个不可变借用
&T
去修改内部的对象 - 线程安全的有 Mutex 和 RwLock
- 非线程安全的有 Cell 和 RefCell,它们允许通过一个不可变借用
比如双向链表就可以写成
1 | struct Node<T> { |
作者就说这样的话一个 Node 会有一个 RwLock。如果我们希望链表可以被多个线程同时修改,这是有用的,但如果只是希望在整个链表上实现 AXM,就有点 overkill 了。
当然,我觉得始终可以去把整个链表外面包一层来解决 AXM 的问题的吧。
介绍
作者宣称 GhostCell 有如下的特性:
- 支持 internal sharing
- zero cost abstraction
- safe,通过 Coq 证明
- flexible,是 thread-safe 的,对 type T 没有要求
原理
作者说 Rust 将 permission 和 data 绑定了。所以这导致了 AXM 在一个很细的粒度上,比如链表中的每个节点。
因此,GhostCell 将 permission 和 data 分开来。让一个 permission 可以和一整个data 关联,比如整个链表。
这个想法称为 Brand Types,来自于 ST monad 的启发。
GhostCell 表示一个大结构里面的某个小元素。这个大结构有一个 brand 为 'id
。
GhostToken 是一个 Permission,可以用来访问任意的标有 'id
的 GhostCell。
以链表为例,进行如下的修改即可使用 GhostCell。
使用
如下所示,创建一个 vec,其中元素都为 cell 的引用。我修改 vec[n / 2]
的值,然后再读取 cell,会发现 cell 的值也被修改了。这里的逻辑没问题,但是 GhostToken 能够避免 borrow checker 的检查报错。
1 | use ghost_cell::{GhostToken, GhostCell}; |
实现
1 | type InvariantLifetime<'brand> = PhantomData<fn(&'brand ()) -> &'brand ()>; |