简体   繁体   English

为什么我的 RefCell 的零成本替代方案不是实现内部可变性的标准方法?

[英]Why is my zero-cost alternative to RefCell not the standard way of achieving interior mutability?

I've been thinking about why interior mutability in Rust in most cases requires runtime checks (eg RefCell ).我一直在思考为什么在大多数情况下 Rust 的内部可变性需要运行时检查(例如RefCell )。 It looks like I've found a safe alternative without a runtime cost.看起来我找到了一个没有运行时成本的安全替代方案。 I've called the type SafeCell (mainly because it is a safe wrapper around UnsafeCell ), and it allows you to apply any function to the wrapped value without the risk of having the reference escape:我将类型称为SafeCell (主要是因为它是UnsafeCell的安全包装器),它允许您将任何 function 应用于包装值,而不会有引用转义的风险:

struct SafeCell<T> {
    inner: UnsafeCell<T>,
}

impl<T> SafeCell<T> {
    pub fn new(value: T) -> Self {
        Self {
            inner: UnsafeCell::new(value),
        }
    }

    pub fn apply<R, F>(&self, fun: F) -> R
    where
        F: FnOnce(&mut T) -> R,
    {
        // Reference below has a lifetime of the current scope, so if
        // user tries to save it somewhere, borrow checker will catch this.
        let reference: &mut T = unsafe { &mut *self.inner.get() };
        fun(reference)
    }
}

This type can be used for interior mutability like this:这种类型可用于内部可变性,如下所示:

pub struct MySet {
    set: HashSet<i32>,
    unique_lookups: SafeCell<HashSet<i32>>,
}

impl MySet {
    pub fn contains(&self, value: i32) -> bool {
        self.unique_lookups.apply(|lookups| lookups.insert(value));
        self.set.contains(value)
    }

    pub fn unique_lookups_count(&self) -> usize {
        self.unique_lookups.apply(|lookups| lookups.len())
    }
}

Or in conjunction with Rc :或与Rc结合使用:

fn foo(rc: Rc<SafeCell<String>>) {
    rc.apply(|string| {
        if string.starts_with("hello") {
            string.push_str(", world!")
        }
        println!("{}", string);
    });
}

Playground 操场

  1. Are there any safety/soundness issues with this type?这种类型是否存在任何安全/健全性问题?
  2. If not, why is a type like this not a standard way of achieving interior mutability?如果不是,为什么这样的类型不是实现内部可变性的标准方法? It looks like it is as usable as RefCell while providing static lifetime checks as opposed to runtime checks.它看起来像RefCell一样可用,同时提供 static 生命周期检查而不是运行时检查。

There is nothing in your API stopping a user from calling apply again in the closure provided to apply .您的 API 中没有任何内容可以阻止用户在为apply apply This allows there to be multiple simultaneous mutable references to the same data, which is undefined behavior.这允许对同一数据同时存在多个可变引用,这是未定义的行为。

let x = SafeCell::new(0);
x.apply(|y| {
    x.apply(|z| {
        // `y` and `z` are now both mutable references to the same data
        // UB!
        *y = 1;
        *z = 2;
    })
});
x.apply(|y| println!("x: {}", y));

(playground) (操场)

Miri correctly calls this out when it sees the second mutable reference being made. Miri 在看到第二个可变引用时正确地指出了这一点。

error: Undefined Behavior: not granting access to tag <untagged> because incompatible item is protected: [Unique for <1651> (call 1230)]
  --> src/main.rs:20:42
   |
20 |         let reference: &mut T = unsafe { &mut *self.inner.get() };
   |                                          ^^^^^^^^^^^^^^^^^^^^^^ not granting access to tag <untagged> because incompatible item is protected: [Unique for <1651> (call 1230)]
   |

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM