#垃圾回收 #虚拟机 #gc #收集器 #fun #safety #heap

cell-gc

为Rust编写的虚拟机提供一个有趣的垃圾回收器

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 内存管理

MIT 许可证

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 类型,在这种情况下是 IntListRefcell_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) 声明的任何类型实现 DropClone。在Rust的完整意义上是安全的(它不会导致崩溃或未定义的行为,只要你的 .drop().clone() 方法不包含 unsafe),但它不会按照你的期望工作。你的 .drop().clone() 方法将不会在预期时被调用;它们会在其他毫无意义的时候被调用。

所以不要这么做!安全的替代方案是在你的值(实现了DropClone的值)周围放置一个BoxRc,并将其用作GC堆结构的字段。

为什么叫“cell-gc”?

在cell-gc中,每个GC管理的对象的每个字段都是可变的。你不能直接获取数据引用;相反,你使用方法来获取和设置值。

就像每个字段都是一个Cell一样。

依赖项

~2MB
~43K SLoC