Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Тема
Описание
Доп.

Cell types - внутренняя изменчивость, мутируют shared ссылки (interior mutability)

std::cell::Cell<T>

std::cell::RefCell<T>

std::cell::OnceCell

std::cell::UnsafeCell

cheats.rs/#cells

Как избежать panic и deadlocks (взаимоблокировок)

rustcamp/1_3_rc_cell

can-the-rust-type-system-prevent-deadlocks

При использовании внутренней изменяемости в Rust, особенно с механизмами типа Cell, RefCell, или Mutex, важно предоставить пользователю API, который исключает возможность паники (panic) или взаимоблокировок (deadlock) при использовании.

Область блокировки не должна каким-либо образом пересекаться.

Вам следует не раскрывать наружу Rc<RefCell<T>> (или Arc<Mutex<T>>) в API, а сделать их внутренней деталью реализации. Таким образом, вы получаете полный контроль над всеми блокируемыми областями внутри ваших методов (никакая область не может просачиваться наружу), что поможет гарантировать отсутствие пересечений и предоставить полностью безопасный API.

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

Внутренняя изменчивость cell types

choosing-your-guarantees

Внутренняя изменчивость - это когда у вас есть неизменяемая shared ссылка &T, но вы можете мутировать T Это полезно, когда вам нужны изменяемые поля внутри неизменяемых структур данных. Иногда требуется иметь несколько ссылок на объект и при этом изменять его. Мы можем использовать типы, в которых применяется паттерн внутренней изменяемости, только если мы можем гарантировать, что правила заимствования будут соблюдаться во время выполнения, несмотря на то, что компилятор не сможет этого гарантировать. В этом случае небезопасный код оборачивается безопасным API, и внешне тип остаётся неизменяемым.

Применяются cell types когда компилятор не в состоянии понят и гарантировать правильность работы с заимствованием В основном применяется в ситуациях, когда сложно или невозможно удовлетворить статическую проверку заимствования. Ослабляют статические ограничения, предотвращающие совпадение изменяемых указателей, и заменяет их на динамические ограничения. Сами гарантии при этом не изменяются.

OnceCell<T> позволяет получить shared &T ссылку на ее внутреннее значение без ее копирования или замены (в отличие от Cell) и без проверок заимствования во время выполнения (в отличие от RefCell) Cell обертывает Copy значения и не проверяет заимствования.

RefCell оборачивает любое значение, имеет проверку заимствования во время выполнения и требует «блокировки» с помощью borrow или borrow_mut, что дает вам неизменяемую или изменяемую ссылку, соответственно.

OnceLock позволяет лениво инициализировать данные, что означает, что они будут инициализированы только один раз при первом обращении, инициализация будет безопасна в многопоточном окружении.

Ни тот, ни другой не безопасны для непосредственного использования в многопоточной среде, они !Sync. Используйте в многопоточной среде вместо:

  • вместо Cell -> Mutex
  • вместо RefCell -> RwLock
  • вместо OnceCell -> std::sync::OnceLock
  • вместоstd::cell::LazyCell -> std::sync::LazyLock

Cell<T> - это тип, который обеспечивает внутреннюю изменяемость без накладных расходов, но только для типов, реализующих типаж Copy. Поскольку компилятор знает, что все данные, вложенные в Cell<T>, находятся на стеке, их можно просто заменять без страха утечки ресурсов.

RefCell<T> - динамические заимствования (отслеживать заимствования во время выполнения) Эти контейнеры позволяют обходить правила заимствования Rust и отслеживать заимствования во время выполнения (так называемое «динамическое заимствование»), что, очевидно, приводит к менее безопасному коду, поскольку непроверенные ошибки при компиляции превращаются в панику во время выполнения.

Короче говоря, Cell имеет Copy семантику и предоставляет значения T, в то время как RefCell имеет move семантику и предоставляет ссылки из-за того что RefCell !Copy он может манипулировать только явно borrow &T и borrow_mut &mut T и проверяется нарушение правил заимствования во время выполнения т.е. вся ответственность на программисте, а Cell не проверяется во время выполнения потому что он Copy это значит что он не будет ссылаться на общие данные, они просто скопируются.

В Box<T> изменяемые ссылки проверяются во время компиляции а в случае с cell types ссылки проверяются во время выполнения и в случае ошибки будет вызван panic! и выход с программы

  • Rc<T> позволяет нескольким владельцам одни и те же данные;
  • Box<T> и RefCell<T> имеют владельцев.
  • Box<T> разрешает неизменяемые или изменчивые учетные данные во время компиляции;
  • Rc<T> позволяет во время компиляции проверять только неизменяемые значения;
  • RefCell<T> позволяет во время выполнения проверять неизменяемые или изменяемые значения.

Что выбрать RefCell или Cell?

Cell — работает без проверок во время выполнения

RefCell делает проверки во время выполнения — он отслеживает, сколько ссылок существует.

Преимущества проверки правил заимствования во время компиляции заключаются в том, что ошибки будут обнаруживаться раньше - ещё в процессе разработки, а производительность во время выполнения не пострадает, поскольку весь анализ завершён заранее.

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

Есть несколько причин, по которым вы можете использовать RefCell вместо Cell:

  • Обернутое значение не реализуется Copy.
  • Есть только RefCell проверки во время выполнения. В некоторых случаях вы предпочтете убить программу, чем рискуете испортить данные.
  • RefCell предоставляет указатели на сохраненное значение, но Cell не делает это.

Как правило, выбирайте, Cell реализует ли ваше обернутое значение Copy (например, примитивные значения, такие как целые числа и числа с плавающей запятой). Если обернутым значением является struct, не реализуется Copy или вам нужны динамически проверяемые заимствования, используйте RefCell вместо этого.

Различие между RefCell и Cell

Наиболее очевидное различие между RefCell и Cell заключается в том, что RefCell проверяют заимствование во время выполнения, а Cell может изменить данные (set) без взятия уникальной ссылки.


use std::cell::RefCell;
use std::cell::Ref;

fn foo(ref_cell: &RefCell) {
    *ref_cell.borrow_mut()=4; //  ❌ panic!
}
fn main() {
    let ref_cell = RefCell::new(0);
    let ref_value:Ref<'_,u32> = ref_cell.borrow();// вызовет panic! во время выполнения! при следующей попытке взять borrow_mut 
    let value:u32 = *ref_cell.borrow() + 1;
    foo(&ref_cell);
    *ref_cell.borrow_mut()=value; //  ❌ panic!
    println!("{:?}",ref_cell);
}

Но Cell может мутировать на месте


fn main(){
    let mut cell = Cell::new(0);
    let value:u32 = cell.get() + 1;
    cell.set(value);  // ✅
    *cell.get_mut()=value+3; // ✅
    assert_eq!(4,cell.get());

// А правила заимствования выполняются, мы не можем иметь несколько mut ссылок
    let ref_value:&mut u32 = cell.get_mut();  
    *cell.get_mut()=value+3; 
    *ref_value=5; //  ❌ panic!
}

Паттерны внутренней изменчивости

Interior-mutability-patterns

Cell
Atomics
Locks
RefCell
Mutex
RWlock
Reentrant Mutex
OnceCell (unsync)
OnceCell (sync)
QCell
TCell and LCell

Случаи внутренней изменчивости

interior-mutability

introducing-mutability-inside-of-something-immutable

Когда внутренняя изменчивость может быть уместна или даже должна использоваться, например:

  1. Внедрение изменчивости внутри чего-то неизменного
  2. Мутирующие реализации Clone
  3. Детали реализации логически неизменяемых методов
  4. Мутация переменных с подсчетом ссылок

Внедрение изменчивости внутри чего-то неизменного указатели подсчета ссылок, такие как Rc и Arc, нуждаются во внутренней изменчивости. Когда вы клонируете эти указатели, счетчик ссылок внутри них должен быть обновлен, независимо от того, являются ли они изменяемыми или нет. Без внутренней изменчивости вы были бы вынуждены всегда использовать изменяемые указатели, что допускало бы изменение внутреннего значения и могло бы быть нежелательным.


use std::cell::Cell;
struct NaiveRc<'a, T: 'a> {
    inner_value: &'a T,
    references: Cell
}
fn main(){
    let x = NaiveRc { inner_value: &1, references: Cell::new(1) };
    x.references.set(2); // работает
    x.inner_value = &2;  // не работает
}

Мутирующие реализации Clone

Клонирование значения с подсчетом ссылок (Rc<T>) требует увеличения счетчика ссылок. С другой стороны, удаление такого значения требует уменьшения счетчика ссылок, но drop работает с изменяемыми ссылками (fn drop(&mut self)), поэтому здесь нет проблем.

Детали реализации логически неизменяемых методов

Например, вы можете захотеть амортизировать время работы дорогостоящего алгоритма, работающего с вашей структурой данных, используя кеш внутри него. Кэш должен иметь возможность обновляться, даже если сама структура данных неизменна.

Своя реализация типа с внутренней изменяемостью (interior mutability)

pub struct MyCell<T> {
    value: T,
}

impl<T> MyCell<T> {
    pub fn new(value: T) -> Self {
        MyCell { value }
    }

    pub fn get(&self) -> &T {
        &self.value
    }

    pub fn set(&self, value: T) {
        // Используем unsafe код, чтобы изменить внутреннее значение без использования &mut
        unsafe {
            let self_mut = self as *const MyCell<T> as *mut MyCell<T>;
            (*self_mut).value = value;
        }
    }
}

std::sync::OnceLock - инициализируется во время выполнения

Позволяет лениво инициализировать данные, что означает, что они будут инициализированы только один раз при первом обращении, инициализация будет безопасна в многопоточном окружении.


use std::sync::OnceLock;

// Глобальная переменная с ленивой инициализацией
static CONFIG: OnceLock = OnceLock::new();

fn get_config() -> &'static str {
    CONFIG.get_or_init(|| {
        println!("Инициализация конфигурации...");
        String::from("Configuration data")
    })
}
fn main() {
    // Первое обращение вызовет инициализацию
    let config = get_config();
    println!("Config: {}", config);

    // Последующие обращения не вызовут инициализацию
    let config_again = get_config();
    println!("Config again: {}", config_again);
}

Сходство с RefCell


fn main(){
    // RefCell - внутренняя изменяемость с проверкой во время выполнения
    use std::cell::RefCell;
    let cell = RefCell::new(None);
    *cell.borrow_mut() = Some(42); // Мутация через неизменяемую ссылку

    // OnceLock - похожая концепция внутренней изменяемости с проверкой во время выполнения, но для однократной инициализации!
    use std::sync::OnceLock;
    let lock = OnceLock::new();
    lock.set(42).unwrap(); // Однократная инициализация
}

Типы Cell предоставляют «внутреннюю» изменяемость.

Другими словами, они содержат данные, которые можно изменять даже если тип не может быть получен в изменяемом виде (например, когда он за указателем & или за Rc<T>)

Аналог для Copy типов в многопоточной среде - Atomic type

Cell может и с не Copy типами работать, только get() не будет работать


use std::cell::Cell;
fn main(){
   let v:Vec = vec![1];
   let mut c = Cell::new(v);
 
   let value:&mut Vec = c.get_mut();
   value.push(2);
   // let value:&Vec = c.get();// doesn't satisfy `Vec: Copy`
    println!("{:?}",value);// [1,2]
}

Иногда требуется иметь несколько ссылок на объект и при этом изменять его.

Для Cell и RefCell


#[derive(Debug)]
struct Data(Cell);
impl Data{
    fn change(&self,new:i32){
        self.0.set(new);
    }
}
#[derive(Debug)]
struct Wrap(Data);
impl Wrap{
    fn change(&self,new:i32){
        self.0.change(new);
    }
}
fn main() {
   let wrap = Wrap(Data(Cell::new(1)));
   let wrap_ref_1 = &wrap;
   let wrap_ref_2 = &wrap;
   wrap_ref_2.change(3);
   wrap_ref_1.change(4); // Кто последний изменил то такие данные и останутся
   assert_eq!(4,wrap.0.0.get());
}

Накладные расходы при использовании Cell<T> отсутствуют (из-за типов реализующих Copy), однако если вы оборачиваете в него большие структуры, есть смысл вместо этого обернуть отдельные поля, поскольку иначе каждая запись будет производить полное копирование структуры.

use std::cell::Cell;
#[derive(Debug,Clone,Copy)]
struct Data(i32);
impl Data{
    fn change(&mut self,new:i32){
        self.0=new;
    }
}
#[derive(Debug)]
struct Wrap(Cell<Data>); ❌
impl Wrap{
    fn change(&mut self,new:i32){ // ❌ придется иметь mut методы
        (self.0.get_mut()).change(new);
    }
}

#[derive(Debug)]
struct Data2(Cell<i32>);
impl Data2{
    fn change(&self,new:i32){
        self.0.set(new);
    }
}
#[derive(Debug)]
struct Wrap2(Data2); ✅
impl Wrap2{
    fn change(&self,new:i32){
        self.0.change(new);
    }
}
fn main() {
   let mut wrap = Wrap(Cell::new(Data(1)));
   let wrap2 = Wrap2(Data2(Cell::new(1)));
   wrap.change(2);
   wrap2.change(2);
   println!("{:?} \n{:?}",wrap,wrap2);
}

Методы std::cell::Cell<T>

  • new() - Создает новую Cell
  • set(10) - Устанавливает содержащееся значение.
  • get() -> T - Возвращает копию содержащегося значения. (для Copy типов)
  • get_mut() -> &mut T - Возвращает изменчивую ссылку на базовые данные. Этот вызов берет Cell изменчиво (во время компиляции), что гарантирует, что мы обладаем единственной ссылкой.
  • as_ptr() -> *mut T - Возвращает необработанный указатель *mut T на базовые данные в этой ячейке.
  • from_mut() - Возвращает &Cell<T> из &mut T
  • as_slice_of_cells() - Возвращает &[Cell<T>] из &Cell<[T]>
  • swap(&cell2) меняет местами значения двух ячеек.
  • replace(10) -> T Заменяет существующее значение и возвращает его.
  • take() Забирает значение ячейки, оставляя на своем месте default() значение
  • into_inner() -> T - Возвращает значение, поглощая ячейку
  • update() -> T - Обновляет содержащееся значение с помощью функции и возвращает новое значение.
  • as_array_of_cells() - Возвращает &[Cell<T>; N] из &Cell<[T; Н]>
  • as_ptr()-> *mut T - Возвращает необработанный указатель *mut T на базовые данные в этой ячейке.
  • as_slice_of_cells() - Возвращает &[Cell<T>] из &Cell<[T]>
  • from_mut() - Возвращает &Cell<T> из &mut T
  • get() -> T - Возвращает копию содержащегося значения. (для Copy типов)
  • get_mut() -> &mut T - Возвращает изменчивую ссылку на базовые данные.
  • into_inner() -> T - Возвращает значение, поглощая ячейку
  • new() - Создает новую Cell
  • replace() -> T - Заменяет существующее значение и возвращает его.
  • set() - Устанавливает содержащееся значение.
  • swap() - Меняет местами значения двух ячеек.
  • take() - Забирает значение ячейки, оставляя на своем месте default() значение
  • update() -> T - Обновляет содержащееся значение с помощью функции и возвращает новое значение.

fn main(){
    use std::cell::Cell;
    let c = Cell::new(5);
}


fn main(){
    let c = Cell::new(5);
    c.set(10);
}


fn main(){
    let c = Cell::new(5);
    let five:i32 = c.get();
}


fn main(){
    let mut c = Cell::new(5);
    *c.get_mut() += 1;
    assert_eq!(c.get(), 6);

    let ref_cell:&mut i32 = c.get_mut();
    *ref_cell=5;
}


fn main(){
    let c = Cell::new(5);
    let ptr:*mut i32 = c.as_ptr();
}


fn main(){
    let slice: &mut [i32] = &mut [1, 2, 3];
    let cell_slice: &Cell<[i32]> = Cell::from_mut(slice);
    let slice_cell: &[Cell] = cell_slice.as_slice_of_cells();
    assert_eq!(slice_cell.len(), 3);
}


fn main(){
    let c1 = Cell::new(5i32);
    let c2 = Cell::new(10i32);
    c1.swap(&c2);
    assert_eq!(10, c1.get());
    assert_eq!(5, c2.get());
}


fn main(){
    let cell = Cell::new(5);
    assert_eq!(cell.get(), 5);
    assert_eq!(cell.replace(10), 5);
    assert_eq!(cell.get(), 10);
}


fn main(){
    let c = Cell::new(5);
    let five = c.take();
    assert_eq!(five, 5);
    assert_eq!(c.into_inner(), 0);
}


fn main(){
    let c = Cell::new(5);
    let five = c.into_inner();
    assert_eq!(five, 5);
}


#![feature(cell_update)]
fn main(){
    let c = Cell::new(5);
    let new = c.update(|x| x + 1);
    assert_eq!(new, 6);
    assert_eq!(c.get(), 6);
}

RefCell<T> также предоставляет внутреннюю изменяемость, но не ограничен только типами, реализующими Copy.

RefCell<T> не Send

Он также применяет правила заимствования во время выполнения, а не во время компиляции.

Аналог для многопоточной среды RwLock, Mutex

В отличие от Cell, можно использовать для любого T

Цена:

  • можно получить панику, если сделать borrow_mut два раза
  • в API будут Ref и RefMut вместо & / &mut
  • маленькая, но не нулевая цена — в runtime поддерживается количество ссылок

Накладные расходы

Тип RefCell<T> полезен, когда вы уверены, что ваш код соответствует правилам заимствования, но компилятор не может понять и гарантировать этого.

RefCell<T> реализует шаблон «read-write lock» во время исполнения, а не во время компиляции, как &T/ &mut T.

RefCell не выделяет память, но содержит дополнительный индикатор «состояния заимствования» (размером в одно слово) вместе с данными.

Во время исполнения каждое заимствование вызывает изменение и проверку счётчика ссылок.

Преимущества проверки правил заимствования во время компиляции заключаются в том, что ошибки будут обнаруживаться раньше - ещё в процессе разработки, а производительность во время выполнения не пострадает, поскольку весь анализ завершён заранее.

Преимущество проверки правил заимствования во время выполнения заключается в том, что определённые сценарии, безопасные для памяти, разрешаются там, где они были бы запрещены проверкой во время компиляции.

RefCell требует, чтобы вы вызывали borrow или borrow_mut (неизменяемые и изменяемые заимствования) перед его использованием, что дает указатель на значение.

Его семантика заимствования идентична внешне изменяемым переменным: вы можете иметь либо изменяемое заимствование внутреннего значения, либо несколько неизменяемых заимствований, поэтому тип ошибки, о которой я упоминал ранее, обнаруживается во время выполнения.

Вызов borrow или borrow_mut взаимно заимствованное значение RefCell вызовет панику. Этот аспект делает RefCell непригодным для использования в параллельном сценарии; вместо этого вам следует использовать потокобезопасный тип (например, a Mutex или a RwLock).

Семантика заимствования: вы можете иметь либо изменяемое заимствование borrow_mut(), либо несколько неизменяемых заимствований borrow(), и эта ошибка заимствования обнаруживается во время выполнения.

Но при этом все же вы можете перезаписать данные не обладая мутабельностью обвертки над RefCell


use std::cell::RefCell;
use std::cell::Ref;

fn foo(ref_cell: &RefCell) {
    *ref_cell.borrow_mut()=4; //  ❌ panic!
}

fn main() {
    let ref_cell = RefCell::new(0);
    let ref_value:Ref<'_,u32> = ref_cell.borrow();// во время выполнения! при следующей попытке взять borrow_mut вызовет panic! 
    let value:u32 = *ref_cell.borrow() + 1;
    foo(&ref_cell);
    *ref_cell.borrow_mut()=value; //  ❌ panic!
    println!("{:?}",ref_cell);
}
$ cargo build ✅
$ ./target/debug/main ❌ panic!

Комбинация Rc<RefCell<T>> (или Arc<RefCell<T>> для многопоточности) позволяет создавать разделяемые изменяемые данные.

Практические примеры:

  • Разделяемого изменяемого состояния
  • Связанных структур данных (графы, деревья)
  • Тестирования и мокапов
  • Шаблона "Наблюдатель" (Observer pattern)

Пример разделяемого изменяемого состояния


use std::rc::Rc;
use std::cell::RefCell;

struct AppState {
    counter: i32,
    is_running: bool,
}
impl AppState {
    fn increment(&mut self) {
        self.counter += 1;
    }
}

struct Component {
    state: Rc>,
}
impl Component {
    fn update(&self) {
        let mut state = self.state.borrow_mut();
        state.increment();
        println!("Counter: {}", state.counter);
    }
}
fn main() {
    let state = Rc::new(RefCell::new(AppState {
        counter: 0,
        is_running: true,
    }));
    
    let comp1 = Component { state: Rc::clone(&state) };
    let comp2 = Component { state: Rc::clone(&state) };
    
    comp1.update(); // Counter: 1
    comp2.update(); // Counter: 2
    comp1.update(); // Counter: 3
}

Пример тестирования и мокапов


use std::rc::Rc;
use std::cell::RefCell;

trait Logger {
    fn log(&self, message: &str);
}

struct MockLogger {
    messages: Rc>>,
}

impl MockLogger {
    fn new() -> Self {
        Self {
            messages: Rc::new(RefCell::new(Vec::new())),
        }
    }
    
    fn get_messages(&self) -> Rc>> {
        Rc::clone(&self.messages)
    }
}

impl Logger for MockLogger {
    fn log(&self, message: &str) {
        self.messages.borrow_mut().push(message.to_string());
    }
}

fn main() {
    let logger = MockLogger::new();
    let messages = logger.get_messages();
    
    logger.log("Hello");
    logger.log("World");
    
    println!("Messages: {:?}", messages.borrow());
    // ["Hello", "World"]
}

--

Но требует осторожности из-за:

  • Возможности циклических ссылок

fn main(){
    // ОПАСНО: может привести к утечкам памяти!
    let a = Node::new(1);
    let b = Node::new(2);

    a.borrow_mut().add_neighbor(Rc::clone(&b));
    b.borrow_mut().add_neighbor(Rc::clone(&a)); // Цикл!
}
  • Проверок во время выполнения (паники)

fn main(){
    let data = Rc::new(RefCell::new(42));

    let borrow1 = data.borrow(); // Нормально
    // let borrow2 = data.borrow_mut(); // ПАНИКА! Уже есть неизменяемая ссылка
}
  • Немного больших накладных расходов

Еще пример про разделяемые изменяемые данные

С помощью RefCell<T> в Cons определении мы можем изменить значение, хранящееся во всех списках:


#[derive(Debug)]
enum List {
    Cons(Rc>, Rc),
    Nil,
}

use List::{Cons, Nil};
use std::rc::Rc;
use std::cell::RefCell;

fn main() {
    let value = Rc::new(RefCell::new(5));

    let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));

    let b = Cons(Rc::new(RefCell::new(6)), Rc::clone(&a));
    let c = Cons(Rc::new(RefCell::new(10)), Rc::clone(&a));

    *value.borrow_mut() += 10;

    println!("a after = {:?}", a);
    println!("b after = {:?}", b);
    println!("c after = {:?}", c);
}
  • as_ptr() - Возвращает необработанный указатель *mut T на базовые данные в этой ячейке.
  • borrow() - заимствует завернутое значение
  • borrow_mut() - Изменяемо заимствует завернутое значение
  • get_mut() - Возвращает изменчивую ссылку на базовые данные. (без динамических проверок заимствования)
  • into_inner() - Возвращает значение, поглощая ячейку
  • new() - Создает новый файл RefCell
  • replace() - Заменяет завернутое значение новым, возвращая старое // std::mem::replace
  • replace_with() - Заменяет завернутое значение новым, вычисленным из f
  • swap() - Меняет местами завернутое значение self на завернутое значение other (std::mem::swap)
  • take() - Принимает завернутое значение, оставляя Default::default() его на месте
  • try_borrow() - Неизменно заимствует завернутое значение, возвращая ошибку, еще существует mut заимствование.
  • try_borrow_mut() - Изменяемое заимствует завернутое значение, возвращая ошибку, если значение в настоящее время заимствовано.
  • try_borrow_unguarded() - Неизменяемо заимствует обернутое значение, возвращая ошибку, если значение уже mut заимствовано
  • undo_leak() - Отменить эффект утечки защиты
  • replace(6) - Заменяет завернутое значение новым, возвращая старое // std::mem::replace
  • into_inner() - Возвращает значение, поглощая ячейку
  • replace_with() - Заменяет завернутое значение новым, вычисленным из f
  • swap() - Меняет местами завернутое значение self на завернутое значение other (std::mem::swap)

fn main(){
    let c = std::cell::RefCell::new(5);
    let old_value = c.replace(6);//Заменяет завернутое значение новым, возвращая старое // std::mem::replace
    let six = c.into_inner();//разименовывание RefCell, возвращая завернутое значение

    println!("{} {}",six, old_value);//6 5
}


fn main(){
    let cell = RefCell::new(5);
    let old_value = cell.replace_with(|&mut old| old + 1);
    assert_eq!(old_value, 5);
    assert_eq!(cell, RefCell::new(6));
}


fn main(){
    use std::cell::RefCell;
    let c = RefCell::new(5);
    let d = RefCell::new(6);
    c.swap(&d);
    assert_eq!(c, RefCell::new(6));
    assert_eq!(d, RefCell::new(5));
}
  • as_ptr() Возвращает необработанный указатель *mut T на базовые данные в этой ячейке.

fn main(){
    let c = RefCell::new(5);
    let ptr = c.as_ptr();
}
  • get_mut() Возвращает изменчивую ссылку на базовые данные.

Этот вызов изменяет RefCell изменчиво (во время компиляции), поэтому нет необходимости в динамических проверках.

Однако будьте осторожны: этот метод ожидает, что self будет изменчивым, что обычно бывает не при использовании RefCell. Вместо этого взгляните на метод borrow_mut, если self не изменен.


fn main(){
    let mut c = RefCell::new(5);
    *c.get_mut() += 1;
    assert_eq!(c, RefCell::new(6));
}
  • borrow() ->Ref<T> - заимствует завернутое значение.

Заимствование продолжается до тех пор, пока возвращенный Ref не выйдет из области. Одновременно можно вынести несколько неизменяемых заимствований.


use std::cell::RefCell;
use std::cell::{Ref,RefMut};
fn main(){
    let c = RefCell::new(5);

    let borrowed_five = c.borrow();
    let borrowed_five2 = c.borrow(); 
    An example of panic:

    use std::cell::RefCell;
    use std::thread;

    let result = thread::spawn(move || {
       let c = RefCell::new(5_i32);
       let m:RefMut<'_, i32> = c.borrow_mut();

       let b:Ref<'_, i32> = c.borrow(); // this causes a panic
    }).join();

    assert!(result.is_err());
}
  • try_borrow() - заимствует с проверкой завернутое значение.

Неизменно заимствует завернутое значение, возвращая ошибку, еще существует mut заимствование. Заимствование продолжается до тех пор, пока возвращенный Ref не выйдет из области. Одновременно можно вынести несколько неизменяемых заимствований. Это не паникующий вариант заимствования.


use std::cell::RefCell;
use std::cell::{Ref,RefMut};
fn main(){
    let c = RefCell::new(5_i32);
    {
        let m:RefMut<'_, i32> = c.borrow_mut();
        assert!(c.try_borrow().is_err());
    }

    {
        let m:Ref<'_, i32> = c.borrow();
        assert!(c.try_borrow().is_ok());
    }
}


fn main(){
    let mut nodes: HashMap>> = HashMap::new();
    let node_new:Rc> = Rc::new(RefCell::new(node));
    nodes.insert("key".to_string(), node_new);

    pub fn search(&self,key:&str)->Option<&Node>{
        if let Some(node_rc) = self.nodes.get(key){ 
            if let Ok(node_ref) = node_rc.try_borrow(){// impl RefCell fn try_borrow() 
                return Some(std::cell::Ref::leak(node_ref));
            }
        }
        None
    }
}
  • borrow_mut() -> RefMut<T> - Изменяемое заимствует завернутое значение.

Заимствование продолжается до тех пор, пока возвращаемый RefMut не выйдет из области. Значение не может быть заимствовано, пока этот заем активен.


use std::cell::RefCell;
fn main(){
    let c = RefCell::new(5);

    *c.borrow_mut() = 7;

    assert_eq!(*c.borrow(), 7);
}

An example of panic:


use std::cell::RefCell;
use std::thread;
fn main(){
    let result = thread::spawn(move || {
       let c = RefCell::new(5);
       let m = c.borrow();

       let b = c.borrow_mut(); //  panic
    }).join();

    assert!(result.is_err());
}
  • try_borrow_mut() - Изменяемое заимствует завернутое значение, возвращая ошибку, если значение в настоящее время заимствовано.

Заимствование продолжается до тех пор, пока возвращаемый RefMut не выйдет из области. Значение не может быть заимствовано, пока этот заем активен.

Это не паникующий вариант borrow_mut


fn main(){
    let c = RefCell::new(5);

    {
        let m = c.borrow();
        assert!(c.try_borrow_mut().is_err());
    }

    assert!(c.try_borrow_mut().is_ok());
}

use std::cell::{RefCell, Ref, RefMut};
use std::rc::Rc;

fn main(){
    let mut r:Rc> = Rc::new(RefCell::new("hello".to_owned()));

    {
       // Rс реализует трейт Borrow и Deref т.е. можно получить не мутабельную ссылку
       let ref_cell:Ref = r.borrow();
       let s:String = (&ref_cell).to_string(); 
       println!("{}",s);
    }
     // Rc получение мутабельной ссылки
     let ref_cell_mut:RefMut = r.borrow_mut();
     let s:String = (&ref_cell_mut).to_string();
     println!("{}",s);

    // или так , но один вариант будет отрабатывать,два раза мутабельную ссылку Rc невзять
    // Rc получение мутабельной ссылки 
    if let Some(r_mut) = Rc::get_mut(&mut r){
       let s:&String = &*r_mut.borrow();
       println!("{}",s);
    }
}

cell::Ref<'a, T> и cell::RefMut<'a, T> — умные заимствующие указатели

Оборачивают &T/&mut T и в Drop уменьшают счётчик ссылок на RefCell<T>

Ref::map — проекция:


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

#[derive(Default)]
struct Person { first_name: String, last_name: String }

// Просто достать ссылку из RefCell нельзя
fn main() {
    let cell = RefCell::new(Person::default());
    let p: Ref = cell.borrow();
    let first_name: Ref = Ref::map(p, |it| it.first_name.as_str());
}

Методы std::cell::RefMut

  • filter_map()
  • leak() - Преобразование в изменяемую ссылку на базовые данные.
  • map() - Преобразование данных
  • map_split() - Разбивает a RefMut на несколько RefMut для разных компонентов заимствованных данных.

std::cell::Ref::clone() - Копирует файл Ref


fn main(){
    let ref_cell = RefCell::new(1);
    {
        let ref_value:RefMut<'_,u32> = ref_cell.borrow_mut(); 
        let mut ref_value_2: RefMut<'_, u32> = RefMut::map(ref_value,|v|{*v=*v+1; v});

        assert_eq!(2u32,*ref_value_2);
    }
    assert_eq!(*ref_cell.borrow(), 2u32);
}


fn main(){
    let ref_cell = RefCell::new(1);
    {
        let ref_value:RefMut<'_,u32> = ref_cell.borrow_mut(); 
        let mut res_ref_value_2:Result ,_> = RefMut::filter_map(ref_value,|v|{*v=*v+1; Some(v)});

        assert_eq!(2u32,*(res_ref_value_2.unwrap()));
    }
    assert_eq!(*ref_cell.borrow(), 2u32);
}


fn main(){
    #![feature(cell_leak)]
    let ref_cell = RefCell::new(1);
    let ref_value:RefMut<'_,u32> = ref_cell.borrow_mut(); 
    let value:&mut u32 = RefMut::leak(ref_value);
    *value = 2;  
}


fn main(){
    use std::cell::{RefCell, RefMut};

    let cell = RefCell::new([1, 2, 3, 4]);
    let borrow = cell.borrow_mut();
    let (mut begin, mut end) = RefMut::map_split(borrow, |slice| slice.split_at_mut(2));
    assert_eq!(*begin, [1, 2]);
    assert_eq!(*end, [3, 4]);
    begin.copy_from_slice(&[4, 3]);
    end.copy_from_slice(&[2, 1]);
}


fn main(){
    let ref_cell = RefCell::new(1);
    let ref_value:Ref<'_,u32> = ref_cell.borrow(); 
    let ref_value2:Ref<'_,u32> = Ref::clone(&ref_value);
}

Пример улучшения кода используя RefCell

Что бы взять мутабельную ссылку в обвертке Rc нужно чтобы была одна strong ссылка и 0 weak ссылок (иначе в режиме unsafe и ночной сборке использовать get_mut_unchecked) Либо просто использовать обвертку Rc<RefCell<T>> и через borrow_mut получить мутабельную ссылку на данные


use std::collections::HashMap;
use std::cell::{RefCell, RefMut, Ref};

#[derive(Debug)]     
struct Person(String);
impl Person{
    fn show(&self){  println!("{}",self.0);}
    fn get_key(&self)->&str{ self.0.as_str() }
    fn set_key(&mut self,key:&str){ self.0=key.to_owned(); }
}
fn main(){
    {
        let mut nodes: HashMap> = HashMap::new();
        let person_rc:Rc = Rc::new(Person("hello".to_string()));
        nodes.insert("person 1".to_string(), person_rc);
        if let Some(ref mut person_mut_rc) = nodes.get_mut("person 1") {
            unsafe {
                let person_mut:&mut Person = Rc::get_mut_unchecked(person_mut_rc);// если get_mut неразрешено
                person_mut.set_key("new key 3");
            }   
        }
        if let Some(ref person_rc) = nodes.get("person 1") {
            println!("{}",person_rc.get_key());
        }
    }

    // Что бы избавиться от необходимости брать изменяемую ссылку с HashMap и Rc, можно обернуть данные в RefCell
    {
        //use std::borrow::BorrowMut;// мешает, выдает ошибку:  no method named `set_key` found for reference `&&Rc>`
        let mut nodes: HashMap>> = HashMap::new();
        let person_rc:Rc> = Rc::new(RefCell::new(Person("hello3".to_string())));
        nodes.insert("person 1".to_string(), person_rc);
        if let Some(ref person_rc) = nodes.get("person 1") {
           let mut reference = person_rc.borrow_mut();
           (*reference).set_key("hello2");
           println!("{:?}", reference.get_key());
        } 
    }
}

Применение внутренней изменчивости для Mock тестирования

pub trait Messenger {
    fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: 'a + Messenger> {
    messenger: &'a T,
    value: usize,
    max: usize,
}

struct MockMessenger {
    sent_messages: RefCell<Vec<String>>,
}
impl MockMessenger {
    fn new() -> MockMessenger {
        MockMessenger { sent_messages: RefCell::new(vec![]) }
    }
}
// Реализуем трейт в котором значение не изменяемое , сделаем его изменяемым в своей реализации
impl Messenger for MockMessenger {
    fn send(&self, message: &str) {
        self.sent_messages.borrow_mut().push(String::from(message));
    }
}
#[test]
fn it_sends_an_over_75_percent_warning_message() {
    let mock_messenger = MockMessenger::new();
    let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);

    limit_tracker.set_value(80);
    assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);
}

Плохой вариант реализации списка

Пользователь нашего API знает детали реализации! Что это дает? Где элегантная абстракция ?

Граф можно представить несколькими способами. Чтобы проиллюстрировать, как на практике работает внутренняя изменяемость (interior mutability), выберем самое простое представление: список узлов.

Каждый узел имеет внутреннее значение и список смежных узлов, с которыми он соединён (через направленное ребро).

Этот список смежных узлов не может быть единственным владельцем этих узлов, иначе каждый узел имел бы не более одного ребра к другому узлу, и граф не смог бы владеть этими узлами.

Нам нужно обернуть Node в контейнер с подсчётом ссылок, такой как Rc или Arc. Если реализовывать только через Rc то вы можете вызвать, get_mut чтобы получить Option<&mut T>, но это сработает только один раз: get_mut возвращает только изменяемую ссылку, как если бы есть только одна «сильная» ссылка на значение

Мы выберем Rc, так как это учебный пример. Однако Rc<T> и Arc<T> обеспечивают безопасность памяти, выдавая только разделяемые (т.е. неизменяемые) ссылки на оборачиваемый объект, а нам нужна изменяемость, чтобы соединять узлы друг с другом.

Решение этой проблемы — обернуть Node в Cell или RefCell, чтобы восстановить изменяемость. Мы используем RefCell, потому что Node<T> не реализует Copy (мы не хотим иметь независимые копии узлов!).


use std::cell::RefCell;
use std::rc::Rc;
// Представляет ссылку на узел.
// Это делает код менее повторяющимся и более читаемым.
type NodeRef = Rc>>;

struct Node {
    inner_value: T,
    adjacent: Vec>,
}
impl Node {
    // Создает новый узел без ребер.
    fn new(inner: T) -> Node {
        Node { inner_value: inner, adjacent: vec![] }
    }
    // Добавляет направленное ребро от этого узла к другому узлу.
    fn add_adjacent(&mut self, other: NodeRef) {
        self.adjacent.push(other);
    }
}
struct Graph {
    nodes: Vec>,
}
fn main() {
    let node_1: NodeRef = Rc::new(RefCell::new(Node::new(1)));
    let node_2: NodeRef = Rc::new(RefCell::new(Node::new(2)));
    let node_3: NodeRef = Rc::new(RefCell::new(Node::new(3)));
    // Соединить некоторые узлы (с направленными ребрами/directed edges)
    (node_1.borrow_mut()).add_adjacent(node_2.clone());
    (node_1.borrow_mut()).add_adjacent(node_3.clone());
    (node_2.borrow_mut()).add_adjacent(node_1.clone());
    (node_3.borrow_mut()).add_adjacent(node_1.clone());
    // Добавим узлы в граф 
    let mut graph = Graph { nodes: vec![] };
    graph.nodes.push(node_1);
    graph.nodes.push(node_2);
    graph.nodes.push(node_3);
    // Покажите каждый узел в графе и перечислите его соседей.
    for node in graph.nodes.iter().map(|n| n.borrow()) {
        let value = node.inner_value;
        let neighbours = node.adjacent.iter()
            .map(|n| n.borrow().inner_value)
            .collect::>();
        println!("node ({}) is connected to: {:?}", value, neighbours);
    }
}


use std::cell::RefCell;
use std::rc::Rc;
// Представляет ссылку на узел.
// Это делает код менее повторяющимся и более читаемым.
type NodeRef = Rc>>;

// Частное представление узла.
struct _Node {
    inner_value: T,
    adjacent: Vec>,
}
// Публичное представление узла с некоторым синтаксическим сахаром.
struct Node(NodeRef);

impl Node {
    // Создает новый узел без ребер.
    fn new(inner: T) -> Node {
        let node = _Node { inner_value: inner, adjacent: vec![] };
        Node(Rc::new(RefCell::new(node)))
    }
    // Добавляет направленное ребро от этого узла к другому узлу.
    fn add_adjacent(&self, other: &Node) {
        (self.0.borrow_mut()).adjacent.push(other.0.clone());
    }
}
struct Graph {
    nodes: Vec>,
}
impl Graph {
    fn with_nodes(nodes: Vec>) -> Self {
        Graph { nodes: nodes }
    }
}

// Реализовать изменения:
// 1. Замена RefCell на Cell
// 2. Удаление RefCell и использование Rc>
// 3. Удаление Rc и использование RefCell>
fn main() {
    let node_1 = Node::new(1);
    let node_2 = Node::new(2);
    let node_3 = Node::new(3);

    // Соединить некоторые узлы (с направленными ребрами/directed edges)
    node_1.add_adjacent(&node_2);
    node_1.add_adjacent(&node_3);
    node_2.add_adjacent(&node_1);
    node_3.add_adjacent(&node_1);

    // Добавим узлы в граф
    let graph = Graph::with_nodes(vec![node_1, node_2, node_3]);

    // Покажите каждый узел в графе и перечислите его соседей.
    for node in graph.nodes.iter().map(|n| n.0.borrow()) {
        let value = node.inner_value;
        let neighbours = node.adjacent.iter()
            .map(|n| n.borrow().inner_value)
            .collect::>();
        println!("node ({}) is connected to: {:?}", value, neighbours);
    }
}

Ячейка, в которую можно записать только один раз.

Это позволяет получить shared &T ссылку на ее внутреннее значение без ее копирования или замены (в отличие от Cell) и без проверок заимствования во время выполнения (в отличие от RefCell)

Методы std::cell::OnceCell

  • get()- >Option<&T> - Получает ссылку на базовое значение, None если ячейка пуста
  • get_mut()-> Option<&mut T>
  • get_or_init() - вернуть данные или установить если пусто
  • get_or_try_init()
  • into_inner() - Потребляет ячейку, возвращая завернутое значение.
  • new() - создание пустой ячейке
  • set() - установить данные
  • take() - Извлекает значение из OnceCell, возвращая его обратно в неинициализированное состояние.

fn main(){
    use std::cell::OnceCell;
    let cell = OnceCell::new();
    assert!(cell.get().is_none());// данных нет

    assert_eq!(cell.set(90), Ok(()));
    assert!(cell.get().is_some());// данных есть
    assert_eq!(cell.set(44), Err(44));// больше установить данные нельзя
    assert_eq!(cell.get().unwrap(),&90); 
}


fn main(){
    let cell = OnceCell::new();
    let value = cell.get_or_init(|| 92);
    assert_eq!(value, &92);
    let value = cell.get_or_init(|| unreachable!());
    assert_eq!(value, &92);  
}


fn main(){
    // Установил -> сбросил -> установил -> сбросил ...
    let mut cell = OnceCell::new();
    cell.set("hello".to_string()).unwrap();
    assert!(cell.get().is_some());  
    assert_eq!(cell.take(), Some("hello".to_string()));
    assert_eq!(cell.get(), None);  
    cell.set("hello".to_string()).unwrap();
    assert!(cell.get().is_some());  
    assert_eq!(cell.take(), Some("hello".to_string())); 
}

RefCell<OnceCell<String>> шуточный пример, что бы не таскать mut OnceCell завернем его в RefCell


fn main(){
    let cell:RefCell> = RefCell::new(OnceCell::new());
    cell.borrow_mut().set("hello".to_string()).unwrap();
    {
        let mut bind = cell.borrow_mut();
        let ref_mut:Option<&mut String> = bind.get_mut();
        (*ref_mut.unwrap()).push_str("..."); 
    }

    assert_eq!(cell.borrow().get(), Some(&"hello...".to_string()));
}

std::cell::UnsafeCell

struct.UnsafeCell

Структуры с внутренней изменчивостью основаны на UnsafeCell который использует сырые указатели

Любые типы с внутренней изменчивостью также должны использовать cell::UnsafeCell обертку вокруг значений, которые могут быть изменены через общую ссылку

interior-mutability-behind-the-curtain

Компилятор считает что все &T находящиеся за UnsafeCell могут быть изменчивыми другие только shared

Структуры с внутренней изменчивостью основаны на UnsafeCell который использует сырые уазатели

Небольшой эксперимент показывает, что использование не UnsafeCell требует затрат времени выполнения после оптимизации компилятора и почти неотличимо от «сырых» переменных, как и ожидалось от абстракций Rust с нулевыми накладными расходами.


use std::cell::UnsafeCell;
fn main() {
    let a: UnsafeCell = UnsafeCell::new(123);
    let b: u32 = 456;
    println!("{}, {}", unsafe { *a.get() }, b);
}

Поскольку его API включает небезопасные операции, его использование напрямую немного обременительно. Почти в каждом случае, когда нам нужно внутреннее переменчивость, мы лучше возьмем Cell, RefCell, RwLock и Mutex вместо UnsafeCell.

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

Реентерабельность - частый случай неожиданного алиасинга в однопоточном коде.

Когда функция исполняется при наличии предыдущего ее стекфрейма т.е. дважды Может возникнуть:

  • рекурсия
  • многопоточный код вызвал одну функцию с разных потоков
  • обработка сигналов unix

use std::cell::UnsafeCell;
use std::marker::Sync;
 
pub struct OnceCell {
    // Invariant: written to at most once.
    inner: UnsafeCell>,
}
impl OnceCell {
    pub fn new() -> OnceCell {
     OnceCell { inner: UnsafeCell::new(None) }
    }
    pub fn get(&self) -> Option<&T> {
     unsafe { &*self.inner.get() }.as_ref()
    }
    pub fn set(&self, value: T) -> Result<(), T> {
        let slot = unsafe { &mut *self.inner.get() };
        if slot.is_some() { return Err(value); }
        *slot = Some(value);
        Ok(())
    }
}

// Заведём функцию, удобную в контексте ленивости:
impl OnceCell {
    pub fn get_or_init(&self, f: impl FnOnce() -> T) -> &T {
/*
     // ошибка UB неопределенное поведение если вызывать код в замыкании
    let slot = unsafe { &mut *self.inner.get() };
    match slot {
        None => {
            // smell: вызов callback в unsafe
            *slot = Some(f());
             slot.as_ref().unwrap()
        }
        Some(value) => value,
    }
*/
        // преаращаем UB в ошибку компиляции или Panic
        self.get().unwrap_or_else(|| {
            let inserted = self.set(f());
            assert!(inserted.is_ok(), "reentrancy");
            self.get().unwrap()
        })
    }
}
fn main() {
   // Вызовем рекурсивно для проверки корректности
    let cell: OnceCell> = OnceCell::new();
    let mut r1: Option<&i32> = None;
    let r2: &i32 = cell.get_or_init(|| {
        r1 = Some(&*cell.get_or_init(|| Box::new(1)));
        Box::new(2)
    });
    let r1: &i32 = r1.unwrap();
    println!("{} {}", r1, r2);
}

use std::cell::UnsafeCell;
use std::marker::Sync;

#[derive(Debug)]
struct NotThreadSafe {
    value: UnsafeCell,
}

unsafe impl Sync for NotThreadSafe {}

impl NotThreadSafe {
    fn new(v:T)->Self{
      NotThreadSafe{value:UnsafeCell::new(v)}
    }
}
fn main() {
     let v = NotThreadSafe::new(5_i32);
    
     //let data_i32:i32 = v.value.into_inner();
     //assert_eq!(5,data_i32);
     
     let data = v.value.get();
      if !data.is_null(){
       unsafe{
           let data_i32:i32 = *data;
           assert_eq!(5,data_i32);
       }
}}

Способ передать mut Vec<T> в thread


use std::sync::Arc;
use std::cell::UnsafeCell;
use std::marker::Sync;

#[derive(Debug)]
struct NotThreadSafe {
    value: UnsafeCell>,
}
unsafe impl Sync for NotThreadSafe {}

impl NotThreadSafe {
    fn new(v:Vec)->Self{
       NotThreadSafe{value:UnsafeCell::new(v)}
    }
}
fn main() {
     let v = Arc::new(NotThreadSafe::new(vec![1;5]));
     let mut buff = vec![]; 
     let v_clone_1 = Arc::clone(&v);
     let v_clone_2 = Arc::clone(&v);
     let h1 = std::thread::spawn( move ||{
        let data:*mut Vec = v_clone_1.value.get();
        if !data.is_null(){
            unsafe{
               let data_vec:&mut Vec = &mut *data;
               data_vec[4]=9;
            }
        } 
    });
    buff.push(h1);
        let h2 = std::thread::spawn( move ||{
        let data:*mut Vec = v_clone_2.value.get();
        if !data.is_null(){
            unsafe{
               let data_vec:&mut Vec = &mut *data;
               data_vec[3]=8;
            }
        } 
    });
    buff.push(h2);
    for h in buff{
        h.join().unwrap();
    }
    
    let mut data = v.value.get();
    if !data.is_null(){
        unsafe{
           let data_vec:&Vec = &*data;
           println!("{:?}",data_vec);
           assert_eq!(vec![1, 1, 1, 8, 9],data_vec.clone());
        }
    } 
}