При использовании внутренней изменяемости в Rust, особенно с механизмами типа Cell, RefCell, или Mutex, важно предоставить пользователю API, который исключает возможность паники (panic) или взаимоблокировок (deadlock) при использовании.
Область блокировки не должна каким-либо образом пересекаться.
Вам следует не раскрывать наружу Rc<RefCell<T>> (или Arc<Mutex<T>>) в API, а сделать их внутренней деталью реализации.
Таким образом, вы получаете полный контроль над всеми блокируемыми областями внутри ваших методов (никакая область не может просачиваться наружу), что поможет гарантировать отсутствие пересечений и предоставить полностью безопасный API.
И даже когда нет возможности скрыть защиту блокировок за границей API, можно попробовать закодировать описанное свойство через систему типов, используя типы-оболочки нулевого размера для защиты.
Внутренняя изменчивость - это когда у вас есть неизменяемая 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!
}
Когда внутренняя изменчивость может быть уместна или даже должна использоваться, например:
Внедрение изменчивости внутри чего-то неизменного
Мутирующие реализации Clone
Детали реализации логически неизменяемых методов
Мутация переменных с подсчетом ссылок
Внедрение изменчивости внутри чего-то неизменного
указатели подсчета ссылок, такие как 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;
}
}
}
Позволяет лениво инициализировать данные, что означает, что они будут инициализированы только один раз при первом обращении, инициализация будет безопасна в многопоточном окружении.
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(); // Однократная инициализация
}
Другими словами, они содержат данные, которые можно изменять даже если тип не может быть получен в изменяемом виде (например, когда он за указателем & или за 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), однако если вы оборачиваете в него большие структуры, есть смысл вместо этого обернуть отдельные поля, поскольку иначе каждая запись будет производить полное копирование структуры.
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());
}
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>> для многопоточности) позволяет создавать разделяемые изменяемые данные.
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);
}
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());
}
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)
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()));
}
Структуры с внутренней изменчивостью основаны на UnsafeCell который использует сырые указатели
Любые типы с внутренней изменчивостью также должны использовать cell::UnsafeCell обертку вокруг значений, которые могут быть изменены через общую ссылку
Компилятор считает что все &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
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());
}
}
}