在 rust 中,immutable 的数据结构的性质是非常好的。在大部分函数式语言中,都不允许存在 mutable 的数据。
如果要在不可变数据结构上进行修改,就需要 clone 一份出来。因此:
- 对于一些较大的结构,希望能够尽量复用
- 如果此时只有一份引用,则可以直接获取 mut 引用就地修改
所以有了 Persistent data structures 的概念:
- 每一次修改该结构,都会保留之前的版本
- 历史的版本可以被查询
- 如果历史版本的数据也支持修改,则称为 Full persistence,否则称为 Partial persistence
实现方案
Copy on Write
用一个数组存放所有的历史版本,非常 bruteforce。
Fat node
为每一个 field 维护历史记录。例如
1 | struct Node { |
会变成
1 | struct FatNode { |
查询的过程就有点像是 MVCC 了。给定一个 version,去 lower_bound 找到最大的小于 version 的修改。
容易看到,Fat node 不支持 Full persistence。
Split
Fat Node 不能无限制增长,否则:
- 历史太长
- 查询复杂度上升
- 节点缓存局部性变差
因此,需要将节点 split 为两个节点:
- 新节点记录最新版本的值
- 老节点保留早期历史
- 结构中指向该节点的指针,也在相应版本中被更新为指向新的节点