一个好朋友推荐的书籍, 趁着感恩节假期学习一下! 原文在这里, 网上可以找到很多中文版翻译, 某站上有讲解视频, 不过我没太看过, 不好评价。这个笔记是我学习过程中的一些记录和心得, 欢迎大家指正!有些名词我不知道标准的翻译, 直接使用英文, 包括: list, node, layout…
A Bad Stuck
首先复习函数式编程中list的概念, 递归定义的list属于 sum type
, 类比C语言中的enum
:List a = Empty | Elem a (List a)
第一次尝试:
pub enum List {
Empty,
Elem(i32, List),
}
编译错误!解决方案:引入Box
确定分配在堆上的空间大小, Box
是管理堆分配的指针类型。
#[derive(Debug)]
pub enum List {
Empty,
Elem(i32, Box<List>),
}
以包含两个元素A, B的list为例, 实际的空间分配情况如下。
[] = Stack
() = Heap
[Elem A, ptr] -> (Elem B, ptr) -> (Empty, *junk*)
注意:用Box
分配在堆上, 第一个元素A分配在栈上(以Elem(A, Box<List>)
的形式), 第一个元素B就嵌套在A的Box<List>
里了, 所以分配在堆上, 最后一个元素Empty
只能带着*junk*
. 这样做的缺点: (1) Empty
根本不是一个node; (2) 有的元素在堆上,有的在栈上, 不统一. 改进方案如下:
[ptr] -> (Elem A, ptr) -> (Elem B, *null*)
这个layout解决了上面的两个问题! Empty
不再作为单独的node, 所有元素都在堆上了! 第一种情况中Empty
所在node要时刻准备着, 预留足够的空间给随时可能到来的新元素.
list的拆分和合并可以引发更多问题.
layout 1:
[Elem A, ptr] -> (Elem B, ptr) -> (Elem C, ptr) -> (Empty *junk*)
split off C:
[Elem A, ptr] -> (Elem B, ptr) -> (Empty *junk*)
[Elem C, ptr] -> (Empty *junk*)
layout 2:
[ptr] -> (Elem A, ptr) -> (Elem B, ptr) -> (Elem C, *null*)
split off C:
[ptr] -> (Elem A, ptr) -> (Elem B, *null*)
[ptr] -> (Elem C, *null*)
比较两个layout, 可以发现layout 1毁掉了list本该拥有的移动指针即可调换元素位置的优点, 和layout 2相比较增加了把元素C从堆复制到栈的过程, 是一个