понедельник, 13 января 2020 г.

Управление памятью в Rust. Часть 2.

Сегодня мы более подробно остановимся на понятии “изменяемость” (mutability). Вспомним о том, что переменная или ссылка (&) в Rust может быть изменяемая (с помощью mut), или не изменяемая (по умолчанию). При работе с простыми типами, например i32, все просто. С составными типами все немного сложнее. Если у нас есть структура, включающая другие члены, то при создании изменяемого объекта этого типа (или имея изменяющую ссылку на него), мы можем изменять члены, и соответственно, если объект неизменяемый (или обычная ссылка на него), то и его члены не могут быть изменены. Это называется “внешняя изменяемость” (Exterior mutability).

Все неплохо пока нам не нужно изменить член, оставляя сам тип неизменяемым. Например, нам нужен тип (структура + методы), члены которого мы хотели бы оставить неизменяемыми при создании объекта и работе с ним, но один из методов этого типа, изменяет внутренние данные (поля), и вызов такого метода требует создания изменяемого объекта. И тут появляется понятие “внутренняя изменяемость” (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());
}

Перегуд В.

Комментариев нет:

Отправить комментарий