1个不稳定版本
使用旧的Rust 2015
0.2.1 | 2017年6月29日 |
---|
#82 在 #garbage-collection
在 cell-gc 中使用
19KB
399 行
cell-gc
Rust中使用的简单垃圾收集器。
目标是帮助您快速在Rust中构建虚拟机。因此,此GC设计为
-
安全性
-
不依赖于linters或编译器插件
-
一个与高性能实现一致的API(尽管目前cell-gc并不快)
-
趣味性
注意事项
cell-gc用于虚拟机。因此,假设GC管理的数据不是真正的您的数据;它是您的最终用户的数据。如果您不希望GC管理的每个对象的每个字段都是公共的且可变的,cell-gc不是您项目的GC!
API完全不稳定。我保证我会以会破坏代码的方式改变它;您只需保持跟上,直到事情稳定下来。
cell-gc未设计为支持单个堆的多线程访问(如Java)。相反,您可以为每个线程创建一个堆(如JavaScript)。
目前它不支持具有随机生命周期的许多小型堆(如Erlang),但我有一些想法如何实现这一点。参见问题#7。
如何使用它
使用 cell_gc
有两个部分:GC类型和GC堆。
声明GC类型
声明GC类型实际上非常简单。只需将 #[derive(IntoHeap)]
添加到任何结构体中
extern crate cell_gc;
#[macro_use] extern crate cell_gc_derive;
/// A linked list of numbers that lives in the GC heap.
/// The `#[derive(IntoHeap)]` here causes Rust to define an additional
/// type, `IntListRef`.
#[derive(IntoHeap)]
struct IntList<'h> {
head: i64,
tail: Option<IntListRef<'h>>
}
# fn main(){}
cell_gc
执行以下操作
-
幕后,它生成一些用于垃圾收集的代码,例如标记代码。您无需担心这些。
-
它检查
IntList
的字段是否都是GC安全的。并非所有类型都适合用作堆结构体或枚举的字段。以下是允许的字段类型
- 原始类型,例如
i32
- 使用
#[derive(IntoHeap)]
声明的类型,例如IntList<'h>
和IntListRef<'h>
- 其中
T
具有静态生命周期的Box<T>
- 其中
T
具有静态生命周期的Rc<T>
- 其中
T
是以下任何类型之一的Option<T>
如果你尝试使用其他任何东西,你将收到来自
rustc
的奇怪错误信息。 - 原始类型,例如
-
它为你声明了一个
Ref
类型,在这个例子中是IntListRef
。cell_gc
通过将Ref
粘接到结构体名称的末尾来命名此类型。IntListRef
是指向 GC 管理的IntList
的智能指针。你需要这个,因为cell_gc
不允许你在 GC 堆中的东西拥有正常的 Rust 引用。IntListRef
值保持堆中的IntList
值存活;一旦指向对象的最后一个IntListRef
消失,它就可供垃圾收集使用,最终将被回收。IntListRef
类似于std::rc::Rc
:它是Clone
但不是Copy
,并且调用.clone()
会复制 Ref,而不是它指向的对象。Ref
类型具有用于获取和设置结构体每个字段的访问器方法。例如,IntList
有方法.head()
、.tail()
、.set_head(i64)
和.set_tail(Option<IntListRef>)
。
你也可以为枚举推导出 IntoHeap
,但支持不完整:不会为枚举生成 Ref
类型。不支持元组结构体。
理解堆
这部分尚未很好地记录。但这里有一个例子,使用上面的 IntList
# extern crate cell_gc;
# #[macro_use] extern crate cell_gc_derive;
# #[derive(IntoHeap)]
# struct IntList<'h> {
# head: i64,
# tail: Option<IntListRef<'h>>
# }
use cell_gc::Heap;
fn main() {
// Create a heap (you'll only do this once in your whole program)
let mut heap = Heap::new();
heap.enter(|hs| {
// Allocate an object (returns an IntListRef)
let obj1 = hs.alloc(IntList { head: 17, tail: None });
assert_eq!(obj1.head(), 17);
assert_eq!(obj1.tail(), None);
// Allocate another object
let obj2 = hs.alloc(IntList { head: 33, tail: Some(obj1) });
assert_eq!(obj2.head(), 33);
assert_eq!(obj2.tail().unwrap().head(), 17);
});
}
在您的 main()
函数中使用 Heap::new()
来创建一个堆。使用 heap.enter()
来访问堆(打开一个“堆会话”,hs
)。使用 hs.alloc(v)
在堆中分配值。
GC堆中的向量
一个用于文字冒险游戏的非常简单的“对象”类型
extern crate cell_gc;
#[macro_use] extern crate cell_gc_derive;
use cell_gc::collections::VecRef;
#[derive(IntoHeap)]
struct Object<'h> {
name: String,
description: String,
children: VecRef<'h, ObjectRef<'h>>
}
# fn main() {}
注意,children
是一个 VecRef<'h, ObjectRef<'h>>
;也就是说,它是对一个单独GC分配的 Vec<ObjectRef<'h>>
的引用,这是一个指向其他对象的引用的向量。换句话说,这正好是你希望在Java中使用此声明的字段时会有什么
public ArrayList<Object> children;
由这个宏生成的API看起来像这样
# struct VecRef<'h, T: 'h>(&'h T); // hack to make this compile
struct Object<'h> {
name: String,
description: String,
children: VecRef<'h, ObjectRef<'h>>
}
struct ObjectRef<'h> {
/* all fields private */
# target: &'h Object<'h> // hack to make this compile
}
impl<'h> ObjectRef<'h> {
fn name(&self) -> String
# { unimplemented!(); }
fn set_name(&self, name: String)
# { unimplemented!(); }
fn description(&self) -> String
# { unimplemented!(); }
fn set_description(&self, description: String)
# { unimplemented!(); }
fn children(&self) -> VecRef<'h, ObjectRef<'h>>
# { unimplemented!(); }
fn set_children(&self, children: VecRef<'h, ObjectRef<'h>>)
# { unimplemented!(); }
}
(您可能永远不会真正使用那个 set_children()
方法。相反,您将在创建对象时用向量初始化 children
字段,然后您很可能将现有的向量进行修改而不是创建一个新的。)
您可以使用 hs.alloc(Object { ... })
在堆中分配 Object
,并通过使用 obj1.children().push(obj2)
将一个 Object
设为另一个的子对象。
安全性
只要您在代码中不键入关键字 unsafe
,这个GC就是安全的。[需要引用]
尽管如此,还有一个需要注意的奇怪规则:不要为使用 derive(IntoHeap)
声明的任何类型实现 Drop
或 Clone
。 在Rust的全意义上这个词是安全的(它不会导致崩溃或未定义的行为,只要你的 .drop()
或 .clone()
方法不执行 unsafe
),但它不会按预期工作。您的 .drop()
和 .clone()
方法将不会在您期望的时候被调用;它们会在其他没有意义的时候被调用。
所以不要这么做!安全的替代方案是将一个Box
或Rc
放在你的值(实现了Drop
或Clone
的那个值)周围,并将其用作GC堆结构的字段。
为什么叫“cell-gc”呢?
在cell-gc中,每个GC管理的对象的每个字段都是可变的。你无法获得数据的直接引用;相反,你使用方法来获取和设置值。
就好像每个字段都是一个Cell。
依赖项
~1.5MB
~41K SLoC