1个不稳定版本

使用旧的Rust 2015

0.2.1 2017年6月29日

#82#garbage-collection


cell-gc 中使用

MIT 许可证

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 类型,在这个例子中是 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

# 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) 声明的任何类型实现 DropClone 在Rust的全意义上这个词是安全的(它不会导致崩溃或未定义的行为,只要你的 .drop().clone() 方法不执行 unsafe),但它不会按预期工作。您的 .drop().clone() 方法将不会在您期望的时候被调用;它们会在其他没有意义的时候被调用。

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

为什么叫“cell-gc”呢?

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

就好像每个字段都是一个Cell

依赖项

~1.5MB
~41K SLoC