如何从 Cell<T> 获取 &T

时间:2021-04-17 15:44:33

标签: rust

我需要 fork 流行的 gc crate 来为 Trace + Finalize 实现 Cell。这就是库为 Vec 实现这些的方式:

impl<T: Trace> Finalize for Vec<T> {}
unsafe impl<T: Trace> Trace for Vec<T> {
    custom_trace!(this, {
        for e in this {
            mark(e);
        }
    });
}

我正在尝试对 Cell<T> 执行相同的操作,但它仅适用于 T: Copyas_ref() 中没有 Cell 方法,那么我怎样才能做得更好?

impl<T: Trace> Finalize for Cell<T> {}
unsafe impl<T: Trace + Copy> Trace for Cell<T> {
    custom_trace!(this, {
        // this: &Cell<T>
        mark(&this.get());
    });
}

1 个答案:

答案 0 :(得分:2)

无法从 &T 获取 Cell<T>。事实上,这就是 Cell 的全部意义所在。请允许我解释一下:

Rust 的核心思想之一是,同时发生的别名和变异是各种罪恶的根源。因此 (multiple references (aliasing) & mutation) 不能在 Rust 中发生。有两种特殊情况我们可以排除这种情况:

  • 如果只有一个引用,则不能有多个引用的别名。这就是 &mut T 存在的原因。我们称它为“可变引用”,但这里的重点是编译器保证它是唯一的引用,防止出现别名;在这种情况下,我们可以自由地改变 T 而不用担心。如果我们有活的 &T,情况就会相反:使用 &T 可以使用别名,并且禁止突变。
  • 如果完全没有引用,则同样的论点适用。 Cell<T> 就是这种情况,它不允许从其内部(它的一个单元格)获取 &T。由于没有引用,我们可以随时随意修改值,因为不可能有任何优秀的观察者。这就是为什么您可以使用对 Cell<T> 的不可变引用来修改 T(例如 mycell.set(123))。

这两种特殊情况允许 Rust 在编译时防止别名和变异,而不会给程序员带来太多麻烦。

据您的代码片段判断,这正是 Cell 必须防止的那种情况:mark() 函数采用 &T 并且可能对它,包括存储它、传递它、复制它或获取对 T 成员的引用。 Cell 可以防止引用逃逸到其他作用域中,从而允许在不“害怕”别名的情况下进行突变。

您可能首先要重新考虑为什么会有 Cell。这可能表明T根本不能转义,因为其他地方的代码会使 T 发生变异,然后您将使用停滞/过时的值。

您也许可以使用 RefCell,它是 Cell 的大哥,它使用管理所有访问的代理类型在运行时跟踪别名/变异。您可以&T 中获取 RefCell,但是您的实现必须保证它永远不会尝试在别名时发生变异(编译器无法捕获,但是运行时会导致恐慌)。

相关问题