#arena #arena-allocator #memory-management #allocation #graph #reference #immutability

erased-type-arena

一个带有适当释放操作的类型擦除分配区域

4个版本 (2个重大更新)

0.3.1 2022年3月12日
0.3.0 2021年12月12日
0.2.0 2021年12月7日
0.1.0 2021年9月9日

#1900数据结构

自定义许可证

30KB
497

erased-type-arena

Build Status Code Size Downloads License Version

一个带有适当释放操作的类型擦除分配区域。它与 typed-arena 类似,但是泛型类型参数从区域中被擦除,并且区域能够分配不同类型的值。此外,通过动态检查防止了由于 drop 函数实现不当而导致的潜在使用后释放漏洞。

动机

在100%安全的Rust中实现类似于图的数据结构并不容易,因为图节点可能被多个节点共享,这本质上违反了Rust的所有权规则。克服这一问题的典型方法是在 分配区域 中分配图节点,并且每个节点通过指向内部可变容器的不可变引用(如 RefCell)被多个其他节点共享。我们可以通过以下代码定义来说明这种方法

struct GraphContext {
    node_arena: Arena,
}

impl GraphContext {
    fn alloc_node<'ctx>(&'ctx self) -> &'ctx RefCell<GraphNode<'ctx>> {
        self.node_arena.alloc(RefCell::new(GraphNode {
            other_nodes: Vec::new(),
        }))
    }
}

struct GraphNode<'ctx> {
    other_nodes: Vec<&'ctx RefCell<GraphNode<'ctx>>>,
}

我们可以选择由 bumpalo 包提供的区域分配器作为我们的节点分配区域。在大多数情况下,这都可以正常工作。然而,如果图节点实现了 Drop 特性,由于 bumpalo 提供的区域分配器不支持在区域本身被释放时执行释放函数,因此它没有其他选择。

typed-arena 是另一个提供分配区的 crate,当分配区本身被丢弃时,它会适当地丢弃分配的值。然而,typed-arena 提供的分配区的类型需要一个泛型类型参数,以指示分配区可以分配的值的类型。这个细微的差异使得它在我们的图结构示例中变得不可行,因为现在 GraphContext 的生命周期注解将传播到自身

struct GraphContext<'ctx> {  // The `'ctx` lifetime notation here is clearly inappropriate
    node_arena: Arena<RefCell<GraphContext<'ctx>>>,
}

impl GraphContext {
    fn alloc_node<'ctx>(&'ctx self) -> &'ctx RefCell<GraphNode<'ctx>> {
        self.node_arena.alloc(RefCell::new(GraphNode {
            other_nodes: Vec::new(),
        }))
    }
}

struct GraphNode<'ctx> {
    other_nodes: Vec<&'ctx RefCell<GraphNode<'ctx>>>,
}

为了克服上述分配区的限制,这个 crate 提供了一个分配区,

  • 当分配区本身被丢弃时,适当地丢弃分配的值,就像 typed-arena 所做的那样;
  • 分配区可以分配不同类型的值,并且泛型类型参数从分配区的类型中擦除。相反,泛型类型参数被移动到 alloc 函数。

丢弃安全性

如果分配值的 drop 函数没有正确实现,可能会导致使用后释放漏洞。更具体地说,当分配区本身被丢弃时,分配在分配区中的值的引用可能会悬空。以下示例证明了这一点

struct GraphNode<'ctx> {
    data: i32,
    other_nodes: Vec<&'ctx GraphNode<'ctx>>,
}

impl<'ctx> Drop for GraphNode<'ctx> {
    fn drop(&mut self) {
        let mut s = 0;
        for node in &self.other_nodes {
            // The reference `node` which points to other nodes allocated in the same arena may
            // dangle here.
            s += node.data;
        }
        println!("{}", s);
    }
}

为了解决这个问题,这个 crate 提供了一个安全的包装器 ArenaMut,用于分配值的可变引用。每次安全包装器被 Deref-ed,它都会检查引用的值是否已被丢弃。如果不幸地,引用的值已被丢弃,则程序会引发恐慌,从而防止发生未定义的行为。

用法

Arena 结构体表示一个分配区。

无运行时依赖