Альтернатива нулевой стоимости RefCell - PullRequest
2 голосов
/ 11 апреля 2020

Я думал о том, почему внутренняя изменчивость в Rust в большинстве случаев требует проверки во время выполнения (RefCell), и похоже, что я нашел безопасную альтернативу без затрат времени выполнения. Я назвал тип SafeCell (в основном потому, что это безопасная оболочка для UnsafeCell), и он в основном позволяет применять любую функцию к переносимому значению без риска перехода по ссылке:

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)
    }
}

Этот тип может использоваться для внутренней изменчивости, как это:

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())
    }
}

Или в сочетании с R c:

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

Детская площадка.

Итак, мне было интересно:

  1. Есть ли какие-либо проблемы с безопасностью / надежностью этого типа?
  2. Если нет, то почему такой тип не является стандартным способом достижения внутренней изменчивости ? Похоже, что он так же удобен, как и RefCell, предоставляя stati c проверки времени жизни в отличие от проверок времени выполнения.

1 Ответ

6 голосов
/ 11 апреля 2020

В вашем API нет ничего, что мешало бы пользователю снова вызвать apply в закрытии, предоставленном для apply. Это позволяет иметь несколько одновременных изменяемых ссылок на одни и те же данные, что является неопределенным поведением.

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));

(площадка)

Мири правильно вызывает это, когда видит вторую изменяемую ссылку.

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)]
   |
...