Мутация в Rust: до или после двоеточия? Разбор под капотом

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: загадка двух «mut»

Если вы когда-нибудь писали на Rust, то наверняка видели странную двойственность ключевого слова mut. Оно может стоять как перед именем переменной (let mut x = 5), так и после двоеточия, рядом с типом (&mut T). На первый взгляд — мелочь, но на Stack Overflow эта тема собрала тысячи просмотров. Почему? Потому что за этой мелочью скрывается фундаментальное различие между владением и ссылкой.

В этой статье мы не просто ответим на вопрос «куда ставить mut?». Мы заглянем под капот Rust, разберём, как компилятор обрабатывает мутабельность, и почему это различие — ключ к безопасной работе с памятью. Если вы учите Rust или хотите глубже понять его модель владения — вы по адресу.

Основная часть: два мира — два mut

1. Первый mut: «Я хозяин и могу меняться»

Когда вы пишете:

let mut x = 42;
x = 10; // ок

вы говорите компилятору: «Я создаю переменную x, которая владеет значением 42, и я разрешаю изменять это значение в дальнейшем». Без mut переменная по умолчанию неизменна (иммутабельна). Это осознанный выбор дизайна Rust: по умолчанию — безопасность, а мутация — явное разрешение.

Под капотом: компилятор выделяет место на стеке (или куче, если это Box) и помечает ячейку памяти как «изменяемая». Проверки заимствования (borrow checker) будут следить, чтобы у вас не было одновременно изменяемой и неизменяемой ссылок на эту память. Если бы mut не стоял, любая попытка присвоить новое значение вызвала бы ошибку компиляции.

2. Второй mut: «Я даю тебе ключи, но не отдаю дом»

Теперь пример с ссылкой:

let mut x = 42;
let y = &mut x; // изменяемая ссылка на x
*y = 100;

Здесь &mut T — это не про владение, а про заимствование. Мы говорим: «Я одалживаю x переменной y, причём даю право его менять». Само x остаётся владельцем, но временно передаёт контроль.

Ключевое отличие: mut после двоеточия относится к типу ссылки, а не к переменной. Вы не можете написать let &mut x = &y — это синтаксически неверно. Rust строго различает:

  • let mut x — изменяемая переменная-владелец
  • &mut T — изменяемая ссылка на некоторое значение типа T

Именно это различие путает новичков: визуально «mut» стоит в разных местах, а семантически — решает разные задачи.

3. Почему это важно: borrow checker

Представьте, что вы пишете функцию, которая принимает ссылку:

fn increment(n: &mut i32) {
    *n += 1;
}

fn main() {
    let mut a = 5;
    increment(&mut a);
    println!("{}", a); // 6
}

Если бы мы написали fn increment(n: &i32) (без mut), то не смогли бы изменить n. А если бы забыли mut у a — не смогли бы передать изменяемую ссылку. Компилятор Rust защищает вас от случайного изменения данных, которое могло бы привести к гонке данных или неопределённому поведению.

Под капотом: &mut T — это уникальная (исключительная) ссылка. В любой момент времени может существовать только одна изменяемая ссылка на данные. Это правило гарантирует, что никто другой не читает данные, пока вы их меняете. Неизменяемых ссылок может быть сколько угодно — они только читают.

4. Реальный пример из практики: структуры и методы

Часто путаница возникает при работе с методами:

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

impl Point {
    fn shift(&mut self, dx: i32, dy: i32) {
        self.x += dx;
        self.y += dy;
    }
}

fn main() {
    let mut p = Point { x: 0, y: 0 };
    p.shift(1, 2);
    println!("({}, {})", p.x, p.y); // (1, 2)
}

Обратите внимание: метод shift принимает &mut self — изменяемую ссылку на экземпляр. Если бы мы написали &self, то не смогли бы изменить поля. А если бы переменная p была объявлена без mut, то вызов p.shift() не скомпилировался бы, потому что нельзя получить изменяемую ссылку от неизменяемой переменной.

Это частая ошибка: вы объявили структуру, написали метод с &mut self, но забыли mut при создании экземпляра. Компилятор выдаст ошибку вроде cannot borrow `p` as mutable. И решение — добавить mut перед именем переменной.

5. А что если mut после двоеточия, но не в ссылке?

Иногда можно встретить конструкции, где mut стоит после двоеточия, но не в ссылке:

let x: mut i32 = 5; // Ошибка! Так нельзя.

Это синтаксически неверно. mut после двоеточия возможен только внутри типа — например, Box<mut i32> тоже не существует. Единственное место, где mut после двоеточия корректен — это ссылки (&mut) и иногда в шаблонах (match).

В шаблонах mut может стоять перед именем переменной, но это уже другая история:

let (mut a, b) = (1, 2); // a изменяема, b — нет

Здесь правило то же: mut относится к переменной, а не к типу.

6. Визуальная шпаргалка

Чтобы запомнить раз и навсегда, представьте две ситуации:

  • Вы — владелец данных. Вы решаете, можно ли их менять. let mut x — вы разрешили.
  • Вы — заёмщик. Вам дали доступ к чужим данным, и тип ссылки говорит, можете ли вы их менять. &mut T — вам разрешили.

Если вы видите mut

Блоги

Рекомендаций по книгам