8 个版本
使用旧的Rust 2015
0.2.1 | 2017年6月29日 |
---|---|
0.2.0 | 2016年5月16日 |
0.1.5 | 2016年2月2日 |
0.1.1 | 2016年1月29日 |
#338 in 内存管理
73KB
1K SLoC
cell-gc
用于Rust的简单垃圾回收器。
目标是帮助您快速在Rust中构建虚拟机。因此,这个GC是为以下情况设计的:
-
安全性
-
不依赖于代码检查器或编译器插件
-
一个与高性能实现一致的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>
Box<T>
其中T
具有静态生命周期Rc<T>
其中T
具有静态生命周期Option<T>
其中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
# #[macro_use] 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)
在堆中分配值。
垃圾回收堆中的向量
一个非常简单的文本冒险游戏“对象”类型
#[macro_use] 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声明这样的字段时在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()
方法将不会在预期时被调用;它们会在其他毫无意义的时候被调用。
所以不要这么做!安全的替代方案是在你的值(实现了Drop
或Clone
的值)周围放置一个Box
或Rc
,并将其用作GC堆结构的字段。
为什么叫“cell-gc”?
在cell-gc中,每个GC管理的对象的每个字段都是可变的。你不能直接获取数据引用;相反,你使用方法来获取和设置值。
就像每个字段都是一个Cell一样。
依赖项
~2MB
~43K SLoC