0Pricing
Learn Rust Coding · Урок

Внутренняя изменяемость: RefCell, Cell

Узнайте о `RefCell` и `Cell`, обеспечивающих внутреннюю изменяемость и безопасный изменяемый доступ к данным через неизменяемую ссылку.

«Внутренняя изменяемость: RefCell, Cell» — бесплатный урок Learn Rust Coding на CoddyKit. Это урок 2 из 3. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Learn Rust Coding, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Learn Rust Coding содержит 3 уроков всего.

Что такое внутренняя изменяемость

В Rust правила заимствования обычно не позволяют получить изменяемую ссылку на данные, если у вас уже есть неизменяемая ссылка на них. Это обеспечивает безопасность данных.

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

`Cell<T>`: для копируемых типов

Тип Cell<T> обеспечивает внутреннюю изменяемость для типов, реализующих трейт Copy, например целых чисел, логических значений и символов. Он работает путём замены значения внутри.

  • get(): возвращает копию значения внутри Cell.
  • set(value): заменяет значение внутри Cell новым значением.

Это простой и эффективный вариант для небольших копируемых данных.

Использование `Cell` для обновления значений

Вот простой пример с Cell. Обратите внимание: мы можем изменить значение внутри Cell, хотя сам Cell объявлен неизменяемым.

use std::cell::Cell;

fn main() {
  let my_num = Cell::new(10);
  println!("Initial value: {}", my_num.get());

  my_num.set(20);
  println!("Updated value: {}", my_num.get());

  let x = &my_num;
  x.set(30); // Still works via immutable reference!
  println!("Via immutable ref: {}", my_num.get());
}

`RefCell<T>`: для некопируемых типов

Если для типов, которые не реализуют Copy, нужна внутренняя изменяемость, например для String, Vec или пользовательских структур, используется RefCell<T>.

RefCell позволяет получать изменяемые ссылки на внутренние данные, но проверяет правила заимствования Rust во время выполнения, а не во время компиляции.

Заимствование во время выполнения в `RefCell`

RefCell предоставляет методы, возвращающие умные указатели:

  • borrow(): возвращает Ref — неизменяемый умный указатель. Одновременно можно иметь несколько неизменяемых заимствований.
  • borrow_mut(): возвращает RefMut — изменяемый умный указатель. Одновременно можно иметь только одно изменяемое заимствование.

Если нарушить эти правила во время выполнения, программа вызовет panic!.

Использование `RefCell` (неизменяемое заимствование)

Здесь с помощью borrow() мы получаем неизменяемую ссылку на String внутри RefCell. Мы можем вывести её содержимое.

use std::cell::RefCell;

fn main() {
  let my_string_cell = RefCell::new(String::from("Hello"));

  let s1 = my_string_cell.borrow();
  println!("Value: {}", *s1); // Dereference Ref to get String

  let s2 = my_string_cell.borrow(); // Multiple immutable borrows are fine
  println!("Another value: {}", *s2);
}

Использование `RefCell` (изменяемое заимствование)

Теперь получим изменяемую ссылку с помощью borrow_mut(). Это позволяет изменить String. Обратите внимание: как только s_mut входит в область видимости, никакие другие заимствования — изменяемые или неизменяемые — не допускаются.

use std::cell::RefCell;

fn main() {
  let my_string_cell = RefCell::new(String::from("World"));

  { // Scope for the mutable borrow
    let mut s_mut = my_string_cell.borrow_mut();
    s_mut.push_str(", Rust!");
  }
  // s_mut is out of scope here, so we can borrow again

  let s_final = my_string_cell.borrow();
  println!("Final value: {}", *s_final);
}

Паника во время выполнения у `RefCell`

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

Запустите этот код, чтобы увидеть это в действии! Он завершится с ошибкой «заимствование уже используется».

use std::cell::RefCell;

fn main() {
  let my_value = RefCell::new(vec![1, 2, 3]);

  let _first_mut_borrow = my_value.borrow_mut();
  println!("First mutable borrow is active.");

  // This line will cause a runtime panic!
  // Try commenting it out to see the program run successfully.
  let _second_mut_borrow = my_value.borrow_mut();

  println!("This line will not be reached.");
}

Итоги: `Cell` и `RefCell`

Выбор между Cell и RefCell зависит от типа данных и того, как именно нужно с ними работать:

  • Cell<T>: используйте для типов, реализующих Copy. Этот тип заменяет значение целиком и обычно проще и производительнее.
  • RefCell<T>: используйте для типов, не реализующих Copy. Он предоставляет ссылки на внутренние данные и проверяет правила заимствования во время выполнения, позволяя выполнять более сложные изменения.

Быстрая проверка: Cell или RefCell?

У вас есть struct, в котором нужно хранить изменяемый счётчик (u32) и изменяемый список имён (Vec<String>), причём оба поля нужно обновлять через неизменяемую ссылку на struct. Какие типы внутренней изменяемости вы выберете для каждого поля?

Повторение: внутренняя изменяемость

Вы узнали о шаблоне внутренней изменяемости в Rust, который позволяет безопасно и контролируемо изменять данные через неизменяемую ссылку.

  • Cell<T>: для типов Copy; заменяет внутреннее значение.
  • RefCell<T>: для типов, не реализующих Copy; предоставляет изменяемые и неизменяемые ссылки с проверкой во время выполнения.
  • Нарушение правил RefCell приводит к панике во время выполнения, что обеспечивает безопасность.

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

Часто задаваемые вопросы

Урок «Внутренняя изменяемость: RefCell, Cell» бесплатный?

Да — полный текст урока «Внутренняя изменяемость: RefCell, Cell» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Learn Rust Coding, подпишись на CoddyKit PRO. Курс Learn Rust Coding содержит 3 уроков всего.

Чему я научусь в уроке «Внутренняя изменяемость: RefCell, Cell»?

Узнайте о `RefCell` и `Cell`, обеспечивающих внутреннюю изменяемость и безопасный изменяемый доступ к данным через неизменяемую ссылку. Ты практикуешь Learn Rust Coding с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать Learn Rust Coding?

Предыдущий опыт не требуется. Learn Rust Coding на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 3.

Сколько времени занимает урок «Внутренняя изменяемость: RefCell, Cell»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке Learn Rust Coding?

Да. Каждый урок Learn Rust Coding включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Умные указатели Box, Rc и Arc
  2. Внутренняя изменяемость: RefCell, Cell
  3. Бесстрашная многопоточность
← Назад к Learn Rust Coding