RefCell<T> и паттерн внутренней изменяемости
Внутренняя изменяемость – это паттерн проектирования в Rust, который
позволяет изменять данные даже тогда, когда на эти данные есть неизменяемые
ссылки; обычно правила заимствования запрещают такое действие. Чтобы изменять
данные, этот паттерн использует unsafe-код внутри структуры данных, чтобы
ослабить обычные правила Rust, управляющие изменением и заимствованием.
Небезопасный код сообщает компилятору, что мы проверяем правила вручную, а не
полагаемся на то, что компилятор проверит их за нас; подробнее мы обсудим
небезопасный код в главе 20.
Мы можем использовать типы, применяющие паттерн внутренней изменяемости, только
когда можем гарантировать, что правила заимствования будут соблюдены во время
выполнения, даже если компилятор не может этого гарантировать. Используемый
unsafe-код при этом обернут в безопасный API, а внешний тип по-прежнему
остается неизменяемым.
Изучим эту концепцию на примере типа RefCell<T>, который следует паттерну
внутренней изменяемости.
Проверка правил заимствования во время выполнения
В отличие от Rc<T>, тип RefCell<T> представляет единоличное владение
данными, которые он хранит. Что же отличает RefCell<T> от типа вроде
Box<T>? Вспомните правила заимствования, которые вы изучили в главе 4:
- В любой момент времени у вас может быть либо одна изменяемая ссылка, либо любое количество неизменяемых ссылок (но не то и другое одновременно).
- Ссылки всегда должны быть допустимыми.
Со ссылками и Box<T> инварианты правил заимствования проверяются во время
компиляции. С RefCell<T> эти инварианты проверяются во время выполнения.
Со ссылками, если вы нарушите эти правила, вы получите ошибку компилятора. С
RefCell<T>, если вы нарушите эти правила, ваша программа вызовет панику и
завершится.
Преимущества проверки правил заимствования во время компиляции в том, что ошибки будут обнаружены раньше в процессе разработки, и это не влияет на производительность во время выполнения, потому что весь анализ уже выполнен заранее. По этим причинам проверка правил заимствования во время компиляции – лучший выбор в большинстве случаев, и именно поэтому это поведение в Rust используется по умолчанию.
Преимущество проверки правил заимствования во время выполнения состоит в том, что становятся разрешены некоторые безопасные с точки зрения памяти сценарии, которые были бы запрещены проверками во время компиляции. Статический анализ, такой как компилятор Rust, по своей природе консервативен. Некоторые свойства кода невозможно определить, анализируя код: самый известный пример – проблема остановки, которая выходит за рамки этой книги, но является интересной темой для изучения.
Поскольку некоторый анализ невозможен, если компилятор Rust не может быть
уверен, что код соблюдает правила владения, он может отклонить корректную
программу; в этом смысле он консервативен. Если бы Rust принимал некорректную
программу, пользователи не смогли бы доверять гарантиям, которые дает Rust.
Однако если Rust отклоняет корректную программу, программисту это доставляет
неудобство, но ничего катастрофического не происходит. Тип RefCell<T>
полезен, когда вы уверены, что ваш код следует правилам заимствования, но
компилятор не способен это понять и гарантировать.
Подобно Rc<T>, RefCell<T> предназначен только для однопоточных сценариев и
даст ошибку компиляции, если вы попробуете использовать его в многопоточном
контексте. О том, как получить функциональность RefCell<T> в многопоточной
программе, мы поговорим в главе 16.
Вот краткое напоминание о причинах выбирать Box<T>, Rc<T> или
RefCell<T>:
Rc<T>допускает нескольких владельцев одних и тех же данных; уBox<T>иRefCell<T>владелец один.Box<T>допускает неизменяемые или изменяемые заимствования, проверяемые во время компиляции;Rc<T>допускает только неизменяемые заимствования, проверяемые во время компиляции;RefCell<T>допускает неизменяемые или изменяемые заимствования, проверяемые во время выполнения.- Поскольку
RefCell<T>допускает изменяемые заимствования, проверяемые во время выполнения, вы можете изменять значение внутриRefCell<T>, даже когда самRefCell<T>неизменяем.
Изменение значения внутри неизменяемого значения – это паттерн внутренней изменяемости. Рассмотрим ситуацию, в которой внутренняя изменяемость полезна, и разберем, как она возможна.
Использование внутренней изменяемости
Одно из следствий правил заимствования состоит в том, что когда у вас есть неизменяемое значение, вы не можете заимствовать его как изменяемое. Например, этот код не скомпилируется:
fn main() {
let x = 5;
let y = &mut x;
}
Если бы вы попытались скомпилировать этот код, то получили бы следующую ошибку:
$ cargo run
Compiling borrowing v0.1.0 (file:///projects/borrowing)
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable
--> src/main.rs:3:13
|
3 | let y = &mut x;
| ^^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut x = 5;
| +++
For more information about this error, try `rustc --explain E0596`.
error: could not compile `borrowing` (bin "borrowing") due to 1 previous error
Однако бывают ситуации, когда значению полезно изменять себя в своих методах,
но для остального кода выглядеть неизменяемым. Код вне методов значения не
смог бы изменять это значение. Использование RefCell<T> – один из способов
получить возможность внутренней изменяемости, но RefCell<T> не обходит
правила заимствования полностью: проверщик заимствований в компиляторе
допускает такую внутреннюю изменяемость, а правила заимствования вместо этого
проверяются во время выполнения. Если вы нарушите правила, вы получите
panic!, а не ошибку компилятора.
Разберем практический пример, где можно использовать RefCell<T> для
изменения неизменяемого значения, и увидим, почему это полезно.
Тестирование с mock-объектами
Иногда во время тестирования программист использует один тип вместо другого, чтобы наблюдать определенное поведение и утверждать, что оно реализовано правильно. Такой замещающий тип называется тестовым дублером. Думайте о нем как о дублере в кино, где человек заменяет актера для выполнения особенно сложной сцены. Тестовые дублеры заменяют другие типы при запуске тестов. Mock-объекты – это конкретные виды тестовых дублеров, которые записывают, что происходит во время теста, чтобы вы могли утверждать, что были выполнены правильные действия.
В Rust нет объектов в том же смысле, в каком объекты есть в других языках, и в стандартной библиотеке Rust нет встроенной функциональности mock-объектов, как в некоторых других языках. Однако вы вполне можете создать структуру, которая будет служить тем же целям, что и mock-объект.
Вот сценарий, который мы будем тестировать: мы создадим библиотеку, которая отслеживает значение относительно максимального значения и отправляет сообщения в зависимости от того, насколько текущее значение близко к максимальному. Например, такую библиотеку можно использовать, чтобы следить за квотой пользователя на количество разрешенных ему вызовов API.
Наша библиотека будет предоставлять только функциональность отслеживания того,
насколько значение близко к максимуму и какие сообщения в какие моменты нужно
отправлять. Ожидается, что приложения, использующие нашу библиотеку,
предоставят механизм отправки сообщений: приложение может показать сообщение
пользователю напрямую, отправить электронное письмо, отправить текстовое
сообщение или сделать что-то еще. Библиотеке не нужно знать эти подробности.
Ей нужно только что-то, что реализует предоставляемый нами трейт с именем
Messenger. Листинг 15-20 показывает код библиотеки.
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}
Одна важная часть этого кода состоит в том, что трейт Messenger имеет один
метод с именем send, который принимает неизменяемую ссылку на self и текст
сообщения. Этот трейт – интерфейс, который должен реализовать наш mock-объект,
чтобы mock можно было использовать так же, как реальный объект. Другая важная
часть в том, что мы хотим протестировать поведение метода set_value у
LimitTracker. Мы можем менять то, что передаем в параметр value, но
set_value ничего не возвращает, чтобы мы могли делать утверждения по
возвращенному значению. Мы хотим иметь возможность сказать: если мы создаем
LimitTracker с чем-то, что реализует трейт Messenger, и конкретным
значением max, то messenger получает команду отправить подходящие сообщения,
когда мы передаем разные числа для value.
Нам нужен mock-объект, который вместо отправки электронного письма или
текстового сообщения при вызове send будет только отслеживать сообщения,
которые ему сказано отправить. Мы можем создать новый экземпляр mock-объекта,
создать LimitTracker, использующий mock-объект, вызвать метод set_value у
LimitTracker, а затем проверить, что mock-объект содержит ожидаемые
сообщения. Листинг 15-21 показывает попытку реализовать mock-объект именно для
этого, но проверщик заимствований ее не разрешит.
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
struct MockMessenger {
sent_messages: Vec<String>,
}
impl MockMessenger {
fn new() -> MockMessenger {
MockMessenger {
sent_messages: vec![],
}
}
}
impl Messenger for MockMessenger {
fn send(&self, message: &str) {
self.sent_messages.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.len(), 1);
}
}
MockMessenger, которую не разрешает проверщик заимствованийЭтот тестовый код определяет структуру MockMessenger, у которой есть поле
sent_messages с Vec значений String для отслеживания сообщений, которые
ей сказано отправить. Мы также определяем связанную функцию new, чтобы было
удобно создавать новые значения MockMessenger, начинающие с пустого списка
сообщений. Затем мы реализуем трейт Messenger для MockMessenger, чтобы
можно было передать MockMessenger в LimitTracker. В определении метода
send мы берем сообщение, переданное как параметр, и сохраняем его в списке
sent_messages у MockMessenger.
В тесте мы проверяем, что происходит, когда LimitTracker получает команду
установить value в значение, которое больше 75 процентов от значения max.
Сначала мы создаем новый MockMessenger, который начнет с пустого списка
сообщений. Затем мы создаем новый LimitTracker и передаем ему ссылку на
новый MockMessenger и значение max, равное 100. Мы вызываем метод
set_value у LimitTracker со значением 80, что больше 75 процентов от
100. Затем мы утверждаем, что список сообщений, который отслеживает
MockMessenger, теперь должен содержать одно сообщение.
Однако у этого теста есть одна проблема, показанная здесь:
$ cargo test
Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference
--> src/lib.rs:58:13
|
58 | self.sent_messages.push(String::from(message));
| ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference in the `impl` method and the `trait` definition
|
2 ~ fn send(&mut self, msg: &str);
3 | }
...
56 | impl Messenger for MockMessenger {
57 ~ fn send(&mut self, message: &str) {
|
For more information about this error, try `rustc --explain E0596`.
error: could not compile `limit-tracker` (lib test) due to 1 previous error
Мы не можем изменить MockMessenger, чтобы отслеживать сообщения, потому что
метод send принимает неизменяемую ссылку на self. Мы также не можем
воспользоваться предложением из текста ошибки и использовать &mut self и в
методе impl, и в определении трейта. Мы не хотим менять трейт Messenger
только ради тестирования. Вместо этого нам нужно найти способ заставить наш
тестовый код корректно работать с существующим дизайном.
Это ситуация, в которой может помочь внутренняя изменяемость! Мы будем хранить
sent_messages внутри RefCell<T>, и тогда метод send сможет изменять
sent_messages, чтобы сохранять увиденные сообщения. Листинг 15-22 показывает,
как это выглядит.
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
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() {
// --snip--
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);
}
}
RefCell<T> для изменения внутреннего значения, когда внешнее значение считается неизменяемымТеперь поле sent_messages имеет тип RefCell<Vec<String>> вместо
Vec<String>. В функции new мы создаем новый экземпляр
RefCell<Vec<String>> вокруг пустого вектора.
В реализации метода send первый параметр по-прежнему является неизменяемым
заимствованием self, что соответствует определению трейта. Мы вызываем
borrow_mut у RefCell<Vec<String>> в self.sent_messages, чтобы получить
изменяемую ссылку на значение внутри RefCell<Vec<String>>, то есть на вектор.
Затем мы можем вызвать push для изменяемой ссылки на вектор, чтобы
отслеживать сообщения, отправленные во время теста.
Последнее изменение, которое нужно сделать, находится в утверждении: чтобы
посмотреть, сколько элементов во внутреннем векторе, мы вызываем borrow у
RefCell<Vec<String>>, чтобы получить неизменяемую ссылку на вектор.
Теперь, когда вы увидели, как использовать RefCell<T>, разберемся, как он
работает!
Отслеживание заимствований во время выполнения
Когда мы создаем неизменяемые и изменяемые ссылки, мы используем синтаксис &
и &mut соответственно. С RefCell<T> мы используем методы borrow и
borrow_mut, которые являются частью безопасного API, принадлежащего
RefCell<T>. Метод borrow возвращает тип умного указателя Ref<T>, а
borrow_mut возвращает тип умного указателя RefMut<T>. Оба типа реализуют
Deref, поэтому мы можем обращаться с ними как с обычными ссылками.
RefCell<T> отслеживает, сколько умных указателей Ref<T> и RefMut<T> в
данный момент активно. Каждый раз, когда мы вызываем borrow, RefCell<T>
увеличивает счетчик активных неизменяемых заимствований. Когда значение
Ref<T> выходит из области видимости, счетчик неизменяемых заимствований
уменьшается на 1. Как и правила заимствования во время компиляции,
RefCell<T> позволяет нам иметь много неизменяемых заимствований или одно
изменяемое заимствование в любой момент времени.
Если мы попытаемся нарушить эти правила, вместо ошибки компилятора, которую
получили бы со ссылками, реализация RefCell<T> вызовет панику во время
выполнения. Листинг 15-23 показывает изменение реализации send из листинга
15-22. Мы намеренно пытаемся создать два изменяемых заимствования, активных в
одной и той же области видимости, чтобы показать, что RefCell<T>
предотвращает это во время выполнения.
pub trait Messenger {
fn send(&self, msg: &str);
}
pub struct LimitTracker<'a, T: Messenger> {
messenger: &'a T,
value: usize,
max: usize,
}
impl<'a, T> LimitTracker<'a, T>
where
T: Messenger,
{
pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {
LimitTracker {
messenger,
value: 0,
max,
}
}
pub fn set_value(&mut self, value: usize) {
self.value = value;
let percentage_of_max = self.value as f64 / self.max as f64;
if percentage_of_max >= 1.0 {
self.messenger.send("Error: You are over your quota!");
} else if percentage_of_max >= 0.9 {
self.messenger
.send("Urgent warning: You've used up over 90% of your quota!");
} else if percentage_of_max >= 0.75 {
self.messenger
.send("Warning: You've used up over 75% of your quota!");
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
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) {
let mut one_borrow = self.sent_messages.borrow_mut();
let mut two_borrow = self.sent_messages.borrow_mut();
one_borrow.push(String::from(message));
two_borrow.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);
}
}
RefCell<T> вызовет паникуМы создаем переменную one_borrow для умного указателя RefMut<T>,
возвращенного из borrow_mut. Затем таким же образом создаем еще одно
изменяемое заимствование в переменной two_borrow. Это создает две изменяемые
ссылки в одной области видимости, что не разрешено. Когда мы запустим тесты
для нашей библиотеки, код из листинга 15-23 скомпилируется без ошибок, но тест
завершится неудачей:
$ cargo test
Compiling limit-tracker v0.1.0 (file:///projects/limit-tracker)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.91s
Running unittests src/lib.rs (target/debug/deps/limit_tracker-e599811fa246dbde)
running 1 test
test tests::it_sends_an_over_75_percent_warning_message ... FAILED
failures:
---- tests::it_sends_an_over_75_percent_warning_message stdout ----
thread 'tests::it_sends_an_over_75_percent_warning_message' panicked at src/lib.rs:60:53:
RefCell already borrowed
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
failures:
tests::it_sends_an_over_75_percent_warning_message
test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
error: test failed, to rerun pass `--lib`
Обратите внимание, что код вызвал панику с сообщением already borrowed: BorrowMutError. Так RefCell<T> обрабатывает нарушения правил заимствования
во время выполнения.
Выбор обнаруживать ошибки заимствования во время выполнения, а не во время
компиляции, как мы сделали здесь, означает, что вы потенциально найдете ошибки
в коде позже в процессе разработки: возможно, только после развертывания кода
в рабочей среде. Кроме того, ваш код понесет небольшие издержки
производительности во время выполнения из-за отслеживания заимствований во
время выполнения, а не во время компиляции. Однако использование RefCell<T>
позволяет написать mock-объект, который может изменять себя, чтобы
отслеживать увиденные сообщения, пока вы используете его в контексте, где
разрешены только неизменяемые значения. Вы можете использовать RefCell<T>
несмотря на эти компромиссы, чтобы получить больше функциональности, чем дают
обычные ссылки.
Разрешение нескольких владельцев изменяемых данных
Обычный способ использовать RefCell<T> – сочетать его с Rc<T>. Вспомните,
что Rc<T> позволяет иметь нескольких владельцев некоторых данных, но дает
только неизменяемый доступ к этим данным. Если у вас есть Rc<T>, который
хранит RefCell<T>, вы можете получить значение, у которого может быть
несколько владельцев и которое вы можете изменять!
Например, вспомните пример cons-списка из листинга 15-18, где мы использовали
Rc<T>, чтобы позволить нескольким спискам совместно владеть другим списком.
Поскольку Rc<T> хранит только неизменяемые значения, мы не можем изменить ни
одно из значений в списке после его создания. Добавим RefCell<T> ради его
способности изменять значения в списках. Листинг 15-24 показывает, что,
используя RefCell<T> в определении Cons, мы можем изменить значение,
хранящееся во всех списках.
#[derive(Debug)]
enum List {
Cons(Rc<RefCell<i32>>, Rc<List>),
Nil,
}
use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;
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(3)), Rc::clone(&a));
let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));
*value.borrow_mut() += 10;
println!("a after = {a:?}");
println!("b after = {b:?}");
println!("c after = {c:?}");
}
Rc<RefCell<i32>> для создания List, который можно изменятьМы создаем значение, являющееся экземпляром Rc<RefCell<i32>>, и сохраняем
его в переменной с именем value, чтобы позже иметь к нему прямой доступ.
Затем мы создаем List в a с вариантом Cons, который хранит value. Нам
нужно клонировать value, чтобы и a, и value владели внутренним значением
5, а не передавать владение от value к a и не заставлять a заимствовать
из value.
Мы оборачиваем список a в Rc<T>, чтобы при создании списков b и c они
оба могли ссылаться на a, как мы делали в листинге 15-18.
После того как мы создали списки в a, b и c, мы хотим добавить 10 к
значению в value. Мы делаем это, вызывая borrow_mut у value, что
использует возможность автоматического разыменования, которую мы обсуждали в
разделе «Где оператор ->?» главы 5,
чтобы разыменовать Rc<T> до внутреннего значения RefCell<T>. Метод
borrow_mut возвращает умный указатель RefMut<T>, и мы используем для него
оператор разыменования, изменяя внутреннее значение.
Когда мы печатаем a, b и c, мы видим, что у всех них измененное значение
15, а не 5:
$ cargo run
Compiling cons-list v0.1.0 (file:///projects/cons-list)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.63s
Running `target/debug/cons-list`
a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
Этот прием довольно удобен! Используя RefCell<T>, мы имеем внешне
неизменяемое значение List. Но мы можем использовать методы RefCell<T>,
которые предоставляют доступ к его внутренней изменяемости, чтобы изменять
наши данные, когда это нужно. Проверки правил заимствования во время
выполнения защищают нас от гонок данных, и иногда стоит обменять немного
скорости на такую гибкость в структурах данных. Обратите внимание, что
RefCell<T> не работает для многопоточного кода! Mutex<T> – потокобезопасная
версия RefCell<T>, и мы обсудим Mutex<T> в главе 16.