RefCell<T> и шаблон внутренней изменяемости

Внутренняя изменяемость (Interior mutability) — это шаблон проектирования в Rust, который позволяет вам изменять данные, даже когда существуют неизменяемые ссылки на эти данные; обычно это действие запрещено правилами заимствования. Чтобы изменять данные, шаблон использует unsafe код внутри структуры данных, чтобы обойти обычные правила Rust, управляющие изменением и заимствованием. Небезопасный код указывает компилятору, что мы проверяем правила вручную, вместо того чтобы полагаться на компилятор для их проверки; мы обсудим небезопасный код более подробно в Главе 20.

Мы можем использовать типы, которые используют шаблон внутренней изменяемости, только когда можем гарантировать, что правила заимствования будут соблюдаться во время выполнения, даже though компилятор не может этого гарантировать. Задействованный unsafe код затем оборачивается в безопасный API, и внешний тип остаётся неизменяемым.

Давайте исследуем эту концепцию, рассмотрев тип RefCell<T>, который следует шаблону внутренней изменяемости.

Обеспечение правил заимствования во время выполнения

В отличие от Rc<T>, тип RefCell<T> представляет единоличное владение данными, которые он содержит. Так что же отличает RefCell<T> от типа вроде Box<T>? Вспомните правила заимствования, которые вы изучили в Главе 4:

  • В любой given момент времени вы можете иметь либо одну изменяемую ссылку, либо любое количество неизменяемых ссылок (но не both).
  • Ссылки должны always быть действительными.

Со ссылками и Box<T> инварианты правил заимствования обеспечиваются во время компиляции. С RefCell<T> эти инварианты обеспечиваются во время выполнения. Со ссылками, если вы нарушите эти правила, вы получите ошибку компилятора. С RefCell<T>, если вы нарушите эти правила, ваша программа запаникует и завершится.

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

Преимущество проверки правил заимствования во время выполнения instead заключается в том, что certain безопасные с точки зрения памяти сценарии then разрешены, где они были бы запрещены проверками во время компиляции. Статический анализ, такой как компилятор Rust, по своей природе консервативен. Некоторые свойства кода невозможно обнаружить, анализируя код: самый известный пример — это проблема остановки (Halting Problem), которая выходит за рамки этой книги, но является интересной темой для исследования.

Поскольку некоторый анализ невозможен, если компилятор Rust не может быть уверен, что код соответствует правилам владения, он может отвергнуть правильную программу; таким образом, он консервативен. Если бы Rust принимал неправильную программу, пользователи не могли бы доверять гарантиям, которые даёт Rust. Однако, если Rust отвергает правильную программу, программист будет неудобен, но nothing катастрофическое не может произойти. Тип 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> позволяет only неизменяемые заимствования, проверяемые во время компиляции; 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> не обходит правила заимствования completely: проверщик заимствований в компиляторе разрешает эту внутреннюю изменяемость, и правила заимствования проверяются во время выполнения instead. Если вы нарушите правила, вы получите panic! вместо ошибки компилятора.

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

Тестирование с помощью Mock-объектов

Иногда во время тестирования программист использует один тип вместо другого, чтобы наблюдать определённое поведение и утверждать, что оно реализовано правильно. Этот тип-заменитель называется test double (тестовый двойник). Думайте об этом в смысле каскадёра в кинопроизводстве, где человек подменяет актёра для выполнения особенно сложной сцены. Тестовые двойники заменяют другие типы, когда мы запускаем тесты. Mock objects (Мок-объекты) — это specific типы тестовых двойников, которые записывают, что происходит во время теста, чтобы вы могли утверждать, что произошли правильные действия.

В Rust нет объектов в том же смысле, как в других языках, и в Rust нет встроенной в стандартную библиотеку функциональности мок-объектов, как в некоторых других языках. Однако вы definitely можете создать структуру, которая будет служить тем же целям, что и мок-объект.

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

Наша библиотека будет предоставлять only функциональность отслеживания близости значения к максимуму и того, какими должны быть сообщения и в какое время. От приложений, использующих нашу библиотеку, ожидается предоставление механизма отправки сообщений: приложение могло бы показывать сообщение пользователю directly, отправлять электронное письмо, отправлять текстовое сообщение или делать что-то ещё. Библиотеке не нужно знать эту деталь. Всё, что ей нужно, — это что-то, что реализует предоставляемый нами трейт с именем 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 и текст сообщения. Этот трейт — это интерфейс, который наш мок-объект должен реализовать, чтобы мок можно было использовать так же, как реальный объект. Другая важная часть заключается в том, что мы хотим протестировать поведение метода set_value у LimitTracker. Мы можем изменять то, что передаём для параметра value, но set_value не возвращает ничего, на что мы могли бы делать утверждения. Мы хотим иметь возможность сказать, что если мы создадим LimitTracker с чем-то, что реализует трейт Messenger, и определённым значением для max, то messenger будет получать указание отправлять соответствующие сообщения, когда мы передаём разные числа для value.

Нам нужен мок-объект, который instead отправки электронного письма или текстового сообщения, когда мы вызываем send, будет only отслеживать сообщения, которые ему велено отправить. Мы можем создать новый экземпляр мок-объекта, создать LimitTracker, который использует мок-объект, вызвать метод set_value у LimitTracker, а затем проверить, что мок-объект имеет ожидаемые нами сообщения. Листинг 15-21 показывает попытку реализовать мок-объект для этого, но проверщик заимствований не позволит это.

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, которая имеет поле 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 solely ради тестирования. Instead, нам нужно найти способ заставить наш тестовый код работать correctly с нашей существующей конструкцией.

Это ситуация, в которой может помочь внутренняя изменяемость! Мы сохраним 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);
    }
}

Поле sent_messages теперь имеет тип RefCell<Vec<String>> instead 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> позволяет нам иметь много неизменяемых заимствований или одно изменяемое заимствование в любой момент времени.

Если мы попытаемся нарушить эти правила, instead получения ошибки компилятора, как было бы со ссылками, реализация 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);
    }
}

Мы создаём переменную one_borrow для умного указателя RefMut<T>, возвращённого из borrow_mut. Затем мы создаём другое изменяемое заимствование таким же образом в переменной two_borrow. Это создаёт две изменяемые ссылки в одной и той же области видимости, что не разрешено. Когда мы запускаем тесты для нашей библиотеки, код в Листинге 15-23 скомпилируется без any ошибок, но тест завершится неудачно:

$ 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> обрабатывает нарушения правил заимствования во время выполнения.

Выбор перехвата ошибок заимствования во время выполнения rather than во время компиляции, как мы сделали здесь, означает, что вы potentially будете находить ошибки в своём коде позже в процессе разработки: possibly не until ваш код будет развёрнут в продакшене. Also, ваш код будет нести небольшие потери производительности во время выполнения в результате отслеживания заимствований во время выполнения rather than во время компиляции. However, использование RefCell<T> делает возможным написание мок-объекта, который может изменять себя для отслеживания сообщений, которые он видел, пока вы используете его в контексте, где разрешены only неизменяемые значения. Вы можете использовать RefCell<T>, несмотря на его компромиссы, чтобы получить больше функциональности, чем предоставляют обычные ссылки.

Разрешение нескольких владельцев изменяемых данных

Распространённый способ использования RefCell<T> — в combination с Rc<T>. Напомним, что Rc<T> позволяет вам иметь нескольких владельцев некоторых данных, но он даёт only неизменяемый доступ к этим данным. Если у вас есть Rc<T>, который содержит RefCell<T>, вы можете получить значение, которое может иметь нескольких владельцев и которое вы можете изменять!

Например, вспомните пример cons list из Листинга 15-18, где мы использовали Rc<T>, чтобы позволить нескольким спискам разделять владение другим списком. Поскольку Rc<T> содержит only неизменяемые значения, мы не можем изменять any из значений в списке once мы их создали. Давайте добавим 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>>, и сохраняем его в переменной с именем value, чтобы мы могли обращаться к нему directly позже. Затем мы создаём List в a с вариантом Cons, который содержит value. Нам нужно клонировать value, чтобы и a, и value имели владение внутренним значением 5 rather than передавая владение от 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 rather than 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>, которые предоставляют доступ к его внутренней изменяемости, чтобы мы могли изменять наши данные, когда нам нужно. Проверки правил заимствования во время выполнения защищают нас от гонок данных, и sometimes стоит обменять немного скорости на эту гибкость в наших структурах данных. Обратите внимание, что RefCell<T> не работает для многопоточного кода! Mutex<T> — это потокобезопасная версия RefCell<T>, и мы обсудим Mutex<T> в Главе 16.