Перейти к содержимому

Ошибка 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» («нельзя присвоить значение неизменяемому аргументу»). Это тот же код ошибки и та же идея.

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

  1. Строка 2 с пометкой first assignment to score («первое присваивание score»): здесь score получила значение. Раз в строке написано let, а не let mut, это значение окончательное.
  2. Строка 4 со значками ^^^^: здесь ты попытался положить новое значение. Rust отказывается.

Часть help: уже показывает исправление: let mut score = 10;. Значки +++ отмечают три символа, которые нужно добавить.

Представь, что let — это запись ручкой, а let mut — карандашом. Оба способа нормальные. Но если ты написал ручкой, стереть потом не получится.

То же правило действует для +=, -=, *= и им подобных. count += 1 — тоже присваивание, поэтому ему тоже нужен mut.

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

Звучит строго, но очень помогает. Когда читаешь let total = ..., ты знаешь, что total сохранит это значение до конца своей области видимости. Не нужно просматривать остаток функции, проверяя, не изменил ли его кто-нибудь. А когда видишь let mut, это ясный сигнал: «следи за этой переменной, она меняется».

Ещё это ловит настоящие баги. Если ты хотел изменить копию, а случайно записал в оригинал, или по ошибке повторно использовал имя, компилятор тебя остановит. А о mut-переменных, которые никогда не меняются, Rust предупреждает (variable does not need to be mutable — «переменной не нужно быть изменяемой»), так что сигнал остаётся честным в обе стороны.

Выбирай этот вариант, когда переменная действительно должна меняться со временем: счётчик, накапливаемая сумма, счёт в игре, состояние. Чаще всего правильное исправление именно это, и именно его предлагает компилятор.

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, as p is 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