Ошибка Rust E0384: cannot assign twice to immutable variable
E0384 значит, что ты дал переменной значение, а потом попытался дать ей другое, но переменная была объявлена без mut. В Rust переменные нельзя менять, пока ты сам этого не разрешишь. Почти каждый натыкается на это в первый же час с Rust, а исправление обычно — три буквы: mut. Сначала на этой странице показано исправление, а потом объяснение, почему Rust так устроен. Другие ошибки из этой серии собраны на странице Ошибки компилятора Rust.
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0384]: cannot assign twice to immutable variable `score` --> src/main.rs:4:5 |2 | let score = 10; | ----- first assignment to `score`3 | println!("Счёт: {score}");4 | score = 15; // ❌ score не mut | ^^^^^^^^^^ cannot assign twice to immutable variable |help: consider making this binding mutable |2 | let mut score = 10; | +++Компилятор пишет: нельзя дважды присвоить значение неизменяемой переменной score. Подсказка предлагает сделать эту привязку изменяемой.
Вот программа, которая выдаёт эту ошибку:
fn main() { let score = 10; println!("Счёт: {score}"); score = 15; // ❌ score не mut println!("Новый счёт: {score}");}Если переменная — параметр функции, формулировка немного меняется: «cannot assign to immutable argument» («нельзя присвоить значение неизменяемому аргументу»). Это тот же код ошибки и та же идея.
Что это значит
Заголовок раздела «Что это значит»Компилятор указывает на два места:
- Строка 2 с пометкой
first assignment to score(«первое присваиваниеscore»): здесьscoreполучила значение. Раз в строке написаноlet, а неlet mut, это значение окончательное. - Строка 4 со значками
^^^^: здесь ты попытался положить новое значение. Rust отказывается.
Часть help: уже показывает исправление: let mut score = 10;. Значки +++ отмечают три символа, которые нужно добавить.
Представь, что let — это запись ручкой, а let mut — карандашом. Оба способа нормальные. Но если ты написал ручкой, стереть потом не получится.
То же правило действует для +=, -=, *= и им подобных. count += 1 — тоже присваивание, поэтому ему тоже нужен mut.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»В Rust каждая переменная по умолчанию неизменяемая. Чтобы её можно было менять, нужно явно сказать mut. Это объясняется в уроке Переменные.
Звучит строго, но очень помогает. Когда читаешь let total = ..., ты знаешь, что total сохранит это значение до конца своей области видимости. Не нужно просматривать остаток функции, проверяя, не изменил ли его кто-нибудь. А когда видишь let mut, это ясный сигнал: «следи за этой переменной, она меняется».
Ещё это ловит настоящие баги. Если ты хотел изменить копию, а случайно записал в оригинал, или по ошибке повторно использовал имя, компилятор тебя остановит. А о mut-переменных, которые никогда не меняются, Rust предупреждает (variable does not need to be mutable — «переменной не нужно быть изменяемой»), так что сигнал остаётся честным в обе стороны.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: добавь mut
Заголовок раздела «Исправление 1: добавь mut»Выбирай этот вариант, когда переменная действительно должна меняться со временем: счётчик, накапливаемая сумма, счёт в игре, состояние. Чаще всего правильное исправление именно это, и именно его предлагает компилятор.
fn main() { let mut score = 10; println!("Счёт: {score}"); score = 15; // ✅ теперь можно println!("Новый счёт: {score}");
let mut count = 0; for _ in 0..3 { count += 1; } println!("Количество: {count}");}Вывод:
Счёт: 10Новый счёт: 15Количество: 3Счётчик — классический случай: let count = 0;, а потом count += 1; внутри цикла дают E0384 на строке с +=. Добавь mut, и всё заработает. См. Делаем переменную изменяемой с помощью mut.
Исправление 2: используй затенение для нового значения
Заголовок раздела «Исправление 2: используй затенение для нового значения»Выбирай этот вариант, когда ты на самом деле не хочешь менять переменную. Тебе нужно новое значение, вычисленное из старого, часто в несколько шагов, а после этого оно остаётся неизменным. Напиши let ещё раз с тем же именем:
fn main() { let price = 20; let price = price + 5; // новая `price`, построенная из старой let price = price * 2; println!("Итоговая цена: {price}");
let input = " 42 "; let input = input.trim(); // всё ещё текст let input: u32 = input.parse().unwrap(); // теперь число println!("Разобрано: {}", input + 1);}Вывод:
Итоговая цена: 50Разобрано: 43Это называется затенением (shadowing): каждый let создаёт совершенно новую переменную, которая скрывает старую. Отсюда два приятных бонуса. Итоговая price неизменяемая, так что потом ничто не изменит её случайно. И тип может меняться, как у input, который из &str становится u32, — с mut так не получится. Подробнее — в разделе Затенение: используем то же имя ещё раз.
Простое правило: если значение меняется снова и снова (в цикле, со временем), используй mut. Если ты готовишь значение в несколько шагов, затенение читается приятнее.
Исправление 3: добавь mut к параметру функции
Заголовок раздела «Исправление 3: добавь mut к параметру функции»Выбирай этот вариант, когда функция хочет менять свою копию аргумента. Параметры тоже неизменяемые, поэтому так не получится:
fn countdown(n: u32) { while n > 0 { println!("{n}..."); n -= 1; // ❌ параметр `n` неизменяемый } println!("Старт!");}error[E0384]: cannot assign to immutable argument `n` --> src/main.rs:4:9 |4 | n -= 1; // ❌ параметр `n` неизменяемый | ^^^^^^ cannot assign to immutable argument |help: consider making this binding mutable |1 | fn countdown(mut n: u32) { | +++Поставь mut перед именем параметра:
fn countdown(mut n: u32) { while n > 0 { println!("{n}..."); n -= 1; } println!("Старт!");}
fn main() { let start = 3; countdown(start); println!("start по-прежнему {start}");}Вывод:
3...2...1...Старт!start по-прежнему 3Заметь, что start в main не изменилась. mut n делает изменяемой только собственную копию функции; вызывающему коду mut не нужен, и его это не затрагивает. Если хочешь, чтобы функция меняла переменную вызывающего кода, принимай &mut u32 и передавай &mut start. См. Функции в Rust и Заимствование.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»Изменение переменной цикла for. Переменная цикла — новая неизменяемая переменная на каждом проходе:
for i in 0..5 { if i == 2 { i += 1; // ❌ cannot assign twice to immutable variable `i` } println!("{i}");}Исправление: менять сам счётчик цикла нужно редко. Пропускай шаги через continue, используй .step_by(2) или, если нужен полный контроль, перейди на цикл while с let mut i. (for mut i in 0..5 компилируется, но меняет только копию текущего прохода и ничего не пропускает.) См. Break и continue и Цикл while.
Сначала объявили, потом присвоили. let total; без значения разрешён, и присвоить ему можно один раз. Второе присваивание даёт E0384:
let total;total = 5;total = 6; // ❌ cannot assign twice to immutable variable `total`Исправление: если значение действительно меняется, используй let mut total. Если ты хотел просто выбрать одно значение, вычисли его одним выражением: let total = if big_order { 6 } else { 5 };. См. If…else.
Накопление суммы. let sum = 0;, а потом sum += price; в цикле — самая частая E0384 из всех. Исправление: let mut sum = 0; или обойдись без переменной: let sum: u32 = prices.iter().sum();. См. Итераторы.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0596: cannot borrow as mutable, as it is not declared as mutable. Та же проблема «забыл
mut», но при вызове изменяющего метода вроде.push(), а не при присваивании. - E0594: cannot assign to
p.hp, aspis not declared as mutable. Встречается, когда присваиваешь полю структуры (p.hp = 5;) или пишешь через ссылку&. Исправление:let mut pили ссылка&mut. См. Изменяемая структура. - E0502: ты попытался изменить значение, пока оно было заимствовано для чтения.
- E0382: ты использовал значение после перемещения.
- Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Ты пишешь let lives = 3;, а позже lives -= 1;. Почему появляется E0384?
2. Ты хочешь один раз привести name в порядок (обрезать пробелы и перевести в верхний регистр) и больше не менять. Что читается лучше всего?
3. fn halve(n: u32) делает n /= 2; и получает E0384. Какое исправление не затрагивает вызывающий код?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0384 — Rust error codes index (на английском): официальное объяснение этой ошибки с примером.
- Переменные и изменяемость — книга «Язык программирования Rust» (перевод на русский): глава о
let,mut, константах и затенении. - Изменяемость — Rust на примерах (перевод на русский): короткий пример
letпротивlet mut, который можно запустить. - Заморозка — Rust на примерах (перевод на русский): как затенение изменяемой переменной неизменяемой её «замораживает».