1. 1. Introduction
  2. 2. Введение
  3. 3. C чего начать
  4. 4. Изучение Rust
    1. 4.1. Угадайка
    2. 4.2. Обедающие философы
    3. 4.3. Вызов кода на Rust из других языков
  5. 5. Синтаксис и семантика
    1. 5.1. Связывание имён
    2. 5.2. Функции
    3. 5.3. Простые типы
    4. 5.4. Комментарии
    5. 5.5. Конструкция `if`
    6. 5.6. Циклы
    7. 5.7. Владение
    8. 5.8. Ссылки и заимствование
    9. 5.9. Время жизни
    10. 5.10. Изменяемость
    11. 5.11. Структуры
    12. 5.12. Перечисления
    13. 5.13. Конструкция `match`
    14. 5.14. Шаблоны сопоставления `match`
    15. 5.15. Синтаксис методов
    16. 5.16. Вектора
    17. 5.17. Строки
    18. 5.18. Обобщённое программирование
    19. 5.19. Типажи
    20. 5.20. Типаж `Drop`
    21. 5.21. Конструкция `if let`
    22. 5.22. Типажи-объекты
    23. 5.23. Замыкания
    24. 5.24. Универсальный синтаксис вызова функций
    25. 5.25. Контейнеры и модули
    26. 5.26. `const` и `static`
    27. 5.27. Атрибуты
    28. 5.28. Псевдонимы типов
    29. 5.29. Приведение типов
    30. 5.30. Ассоциированные типы
    31. 5.31. Безразмерные типы
    32. 5.32. Перегрузка операций
    33. 5.33. Преобразования при разыменовании
    34. 5.34. Макросы
    35. 5.35. Сырые указатели
    36. 5.36. Небезопасный код
  6. 6. Эффективное использование Rust
    1. 6.1. Стек и куча
    2. 6.2. Тестирование
    3. 6.3. Условная компиляция
    4. 6.4. Документация
    5. 6.5. Итераторы
    6. 6.6. Многозадачность
    7. 6.7. Обработка ошибок
    8. 6.8. Выбор гарантий
    9. 6.9. Интерфейс внешних функций
    10. 6.10. Типажи `Borrow` и `AsRef`
    11. 6.11. Каналы сборок
    12. 6.12. Using Rust without the standard library
  7. 7. Нестабильные возможности Rust
    1. 7.1. Плагины к компилятору
    2. 7.2. Встроенный ассемблерный код
    3. 7.3. Без stdlib
    4. 7.4. Внутренние средства
    5. 7.5. Элементы языка
    6. 7.6. Продвинутое руководство по компоновке
    7. 7.7. Тесты производительности
    8. 7.8. Синтаксис упаковки и шаблоны `match`
    9. 7.9. Шаблоны `match` для срезов
    10. 7.10. Ассоциированные константы
    11. 7.11. Пользовательские менеджеры памяти
  8. 8. Глоссарий
  9. 9. Syntax Index
  10. 10. Библиография

Изменяемость (mutability)

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

let x = 5;
x = 6; // ошибка!Run

Изменяемость можно добавить с помощью ключевого слова mut:

let mut x = 5;

x = 6; // нет проблем!Run

Это изменяемое связанное имя. Когда связанное имя изменяемо, это означает, что мы можем поменять связанное с ним значение. В примере выше не то, чтобы само значение x менялось, просто имя x связывается с другим значением типа i32.

Если же вы хотите изменить само связанное значение, вам понадобится изменяемая ссылка:

let mut x = 5;
let y = &mut x;Run

y — это неизменяемое имя для изменяемой ссылки. Это значит, что y нельзя связать ещё с чем-то (y = &mut z), но можно изменить то, на что указывает связанная ссылка (*y = 5). Тонкая разница.

Конечно, вы можете объявить и изменяемое имя для изменяемой ссылки:

let mut x = 5;
let mut y = &mut x;Run

Теперь y можно связать с другим значением, и само это значение тоже можно менять.

Стоит отметить, что mut — это часть шаблона, поэтому можно делать такие вещи:

let (mut x, y) = (5, 6);

fn foo(mut x: i32) {Run

Внутренняя (interior) и внешняя (exterior) изменяемость

Однако, когда мы говорим, что что-либо «неизменяемо» в Rust, это не означает, что оно совсем не может измениться. Мы говорим о «внешней изменяемости». Для примера рассмотрим Arc<T>:

use std::sync::Arc;

let x = Arc::new(5);
let y = x.clone();Run

Когда мы вызываем метод clone(), Arc<T> должна обновить счётчик ссылок. Мы не использовали модификатор mut, а значит x — неизменяемое имя. Мы не можем получить ссылку (&mut 5) или сделать что-то подобное. И что же?

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

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

  • одна или более неизменяемых ссылок (&T) на ресурс,
  • ровно одна изменяемая ссылка (&mut T) на ресурс.

Итак, что же здесь на самом деле является «неизменяемым»? Безопасно ли иметь два указателя на один объект? В случае с Arc<T>, да: изменяемый объект полностью находится внутри самой структуры. По этой причине, метод clone() возвращает неизменяемую ссылку (&T). Если бы он возвращал изменяемую ссылку (&mut T), то у нас были бы проблемы. Таким образом, let mut z = Arc::new(5); объявляет атомарный счётчик ссылок с внешней изменяемостью.

Другие типы, например те, что определены в модуле std::cell, напротив, имеют «внутреннюю изменяемость». Например:

use std::cell::RefCell;

let x = RefCell::new(42);

let y = x.borrow_mut();Run

RefCell возвращает изменяемую ссылку &mut при помощи метода borrow_mut(). А не опасно ли это? Что, если мы сделаем так:

use std::cell::RefCell;

let x = RefCell::new(42);

let y = x.borrow_mut();
let z = x.borrow_mut();Run

Это приведёт к панике во время исполнения. Вот что делает RefCell: он принудительно выполняет проверку правил заимствования во время исполнения и вызывает panic!, если они были нарушены.

Стоит отметить, что тип изменяемости — внутренняя или внешняя — определяется самим типом. Нет способа волшебно превратить значение с внутренней изменяемостью в значение со внешней, и наоборот.

Всё это подводит нас к другим аспектам правил изменяемости Rust. Давайте поговорим о них.

Изменяемость на уровне полей

Изменяемость — это свойство либо ссылки (&mut), либо имени (let mut). Это значит, что, например, у вас не может быть структуры, часть полей которой изменяется, а другая часть — нет:

struct Point {
    x: i32,
    mut y: i32, // нельзя
}Run

Изменяемость структуры определяется при её связывании:

struct Point {
    x: i32,
    y: i32,
}

let mut a = Point { x: 5, y: 6 };

a.x = 10;

let b = Point { x: 5, y: 6};

b.x = 10; // error: cannot assign to immutable field `b.x`Run

Однако, используя Cell<T>, вы можете эмулировать изменяемость на уровне полей:

use std::cell::Cell;

struct Point {
    x: i32,
    y: Cell<i32>,
}

let point = Point { x: 5, y: Cell::new(6) };

point.y.set(7);

println!("y: {:?}", point.y);Run

Это выведет на экран y: Cell { value: 7 }. Мы успешно изменили значение y.