Введение: загадка двух «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