Все неплохо пока нам не нужно изменить член, оставляя сам тип неизменяемым. Например, нам нужен тип (структура + методы), члены которого мы хотели бы оставить неизменяемыми при создании объекта и работе с ним, но один из методов этого типа, изменяет внутренние данные (поля), и вызов такого метода требует создания изменяемого объекта. И тут появляется понятие “внутренняя изменяемость” (Interior mutability), при котором мы имеем неизменяемую ссылку на объект, но можем изменять внутренние данные объекта (поля).
И как мы уже увидели, есть случаи, когда между внешней и внутренней изменяемостью возникает конфликт. Пример такого конфликта — это использование нескольких (неизменяющих) ссылок на один объект или умного указателя Rc. По правилам Rust, мы можем иметь несколько указателей общего владения объектом, которые не могут изменять объект, чтобы избежать гонки за данными. Соблюдение этого правила обеспечивает контролер владения на этапе компиляции. Но это не удобно и нам нужна возможность изменять внутренние данные с соблюдением условий, при которых мы гарантируем блокировку данных (lock), которые хотим изменить, чтобы исключить возможность одновременного доступа к данным из разных мест. Заблокировать данные и обеспечить внутреннюю изменяемость для неизменяемых объектов нам помогут обертки Cell и RefCell. Основное отличие между которыми в том, что RefCell делает проверку владения во время выполнения, а Cell нет. Образно мы помещаем данные в ячейку, которая блокирует доступ к ним из других мест (блоков кода). Эти обертки предназначены для работы в одном потоке.
Cell дает возможность прочитать данные с помощью get(), и изменить с помощью set(). Также есть другие полезные методы. Cell можно применять только к данным, которые имплементируют трейт (интерфейс) Copy. Так как проверки (во время компиляции и выполнения) не проводятся, нужно быть осторожным.
RefCell требует применять borrow() и borrow_mut() для получения нескольких простых или одной изменяющей ссылки на данные, для их чтения или изменения. Проверка времени жизни ссылок проводится во время выполнения. Данные будут заблокированы до того момента пока последняя ссылка не будет уничтожена, что можно принудительно сделать с помощью блока видимости {} или drop(). Обычно нам не нужно копировать обернутые данные и лучше использовать RefCell (перемещение по умолчанию).
Cell предоставляет значения, а RefCell предоставляет ссылки на данные. Выбирайте Cell, если оборачиваете данные, которые имплементируют Copy, т.е. простые типы (int, float). Выбирайте RefCell, если оборачиваете структуры, данные без Copy, или хотите динамически проверять владение.
use std::cell::{Cell, RefCell};
use std::rc::Rc;
#[derive(Debug, Clone, Copy)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let p1 = Point { x: 0, y: 0 };
//Rc<RefCell<Point>>
let rc = Rc::new(RefCell::new(p1));
let rc1 = Rc::clone(&rc);
let rc2 = Rc::clone(&rc);
println!("RefCell: {:?}", rc2);
//RefCell
rc1.borrow_mut().x = 55;
rc1.borrow_mut().y = 55;
println!("RefCell: {:?}", rc2.borrow());
//Rc<Cell<Point>>
let rcc = Rc::new(Cell::new(p1));
let rcc1 = Rc::clone(&rcc);
let rcc2 = Rc::clone(&rcc);
println!("Cell: {:?}", rcc2);
//Cell
//let x = rcc1.get();
rcc1.set(Point { x: 33, y: 33 });
println!("Cell: {:?}", rcc2.get());
}
Перегуд В.
Комментариев нет:
Отправить комментарий