Подтвердить что ты не робот

Когда я могу использовать либо Cell, либо RefCell, который я должен выбрать?

Из std::cell документации, я вижу, что Cell "совместим только с типами, реализующими Copy". Это означает, что я должен использовать RefCell для типов не Copy.

Когда у меня есть тип Copy, есть ли преимущество использования одного типа ячейки над другим? Я предполагаю, что ответ "да", потому что в противном случае оба типа не существовали бы! Каковы преимущества и компромиссы использования одного типа над другим?

Вот глупый, составленный пример, который использует как Cell, так и RefCell для достижения той же цели:

use std::cell::{Cell,RefCell};

struct ThingWithCell {
    counter: Cell<u8>,
}

impl ThingWithCell {
    fn new() -> ThingWithCell {
        ThingWithCell { counter: Cell::new(0) }
    }

    fn increment(&self) {
        self.counter.set(self.counter.get() + 1);
    }

    fn count(&self) -> u8 { self.counter.get() }
}

struct ThingWithRefCell {
    counter: RefCell<u8>,
}

impl ThingWithRefCell {
    fn new() -> ThingWithRefCell {
        ThingWithRefCell { counter: RefCell::new(0) }
    }

    fn increment(&self) {
        let mut counter = self.counter.borrow_mut();
        *counter = *counter + 1;
    }

    fn count(&self) -> u8 { *self.counter.borrow_mut() }
}


fn main() {
    let cell = ThingWithCell::new();
    cell.increment();
    println!("{}", cell.count());

    let cell = ThingWithRefCell::new();
    cell.increment();
    println!("{}", cell.count());
}
4b9b3361

Ответ 1

Я думаю, что важно учитывать другие семантические различия между Cell и RefCell:

  • Cell предоставляет вам значения RefCell со ссылками
  • Cell никогда не паникует, RefCell может паниковать

Представим себе ситуацию, в которой эти различия имеют значение:

let cell = Cell::new(foo);
{
    let mut value = cell.get();
    // do some heavy processing on value
    cell.set(value);
}

В этом случае, если представить себе сложный рабочий процесс с большим количеством обратных вызовов и что Cell является частью глобального состояния, возможно, что содержимое Cell изменяется как побочный эффект "тяжелого обработка", и эти потенциальные изменения будут потеряны, когда value будет записано обратно в Cell.

С другой стороны, аналогичный код с использованием RefCell:

let cell = RefCell::new(foo);
{
    let mut_ref = cell.borrow_mut().unwrap();
    // do some heavy processing on mut_ref
}

В этом случае любая модификация Cell в качестве побочного эффекта "тяжелой обработки" запрещена и приведет к панике. Таким образом, вы уверены, что значение Cell не изменится без использования mut_ref

Я бы определил, что использовать в зависимости от семантики значения, которое он имеет, а не просто с тегом Copy. Если оба приемлемы, то Cell легче и безопаснее другого, и, следовательно, было бы предпочтительнее.

Ответ 2

Вы должны использовать Cell, если сможете.

Cell не проверяет время выполнения. Все, что он делает, это инкапсуляция, которая запрещает наложение псевдонимов и сообщает компилятору, что это внутренне изменяемый слот. В большинстве случаев он должен скомпилировать код, который точно такой же, как если бы тип без обертывания ячеек был там.

Для сравнения, RefCell использует простой счетчик использования для проверки заимствования и изменчивого заимствования во время выполнения, и эта проверка может привести к панике во время выполнения, если вы нарушите, например, исключительность изменчивого заимствования. Возможная паника может стать препятствием для оптимизации.

Есть, по крайней мере, еще одна разница. Cell никогда не позволит вам получить указатель на сохраненное значение. Итак, если вам это нужно, RefCell - единственный выбор.

Ответ 3

TL; DR: Cell когда вы можете.


Длинный ответ: Cell и RefCell имеют аналогичное имя, потому что оба они допускают внутреннюю изменчивость, но они имеют другую цель:

Cell

Это обертка вокруг T которая запрещает делиться ею несколько раз сразу: вы не можете долго хранить внутренние данные. Эта оболочка не имеет накладных расходов, но из-за этого ограничения вы можете выполнять только следующие операции:

  • Установите внутреннее значение,
  • Поменяйте внутреннее значение чем-то другим,
  • Скопируйте внутреннее значение (только тогда, когда T может Copy).

Благодаря своему ограничению, Cell ведет себя как эксклюзивный заимствованный, как a &mut T Поэтому всегда безопасно изменять внутреннее значение. Подвести итоги:

  • Преимущество: отсутствие накладных расходов
  • Преимущество: всегда изменчивое
  • Ограничение: некоторые операции невозможны

RefCell

Это оболочка вокруг T которая "удаляет" RefCell чеки во время компиляции: операции, которые изменяют внутреннее значение, берут общую ссылку &self в RefCell. Обычно это будет небезопасно, но каждая операция модификации сначала проверяет, что значение ранее не было заимствовано. Исключительность изменчивого заимствования проверяется во время выполнения.

Подвести итоги:

  • Ограничение: очень небольшие накладные расходы
  • Ограничение: не всегда изменчиво, если оно ранее было заимствовано (будьте осторожны, некоторые операции могут паниковать в этом случае)
  • Преимущество: вы не ограничены операциями, которые вы можете выполнять

Что вы должны выбрать?

Преимущества и ограничения - это зеркало друг друга. Ответ на ваш вопрос: если ограничения Cell не беспокоят вас, используйте его, потому что помимо этого он имеет только преимущества. Однако, если вы хотите более гибкую внутреннюю изменчивость, используйте RefCell.