Ошибка Rust E0594: cannot assign, as it is not declared as mutable (нет mut)
E0594 значит, что ты написал что-то = значение (или +=, -=, …), но у тебя есть право только читать это «что-то». Обычно это поле структуры в переменной без mut или значение, до которого ты добираешься через ссылку &. Ошибка очень частая, и исправление почти всегда одно слово: mut. Сначала на этой странице показано исправление, а потом объяснение. Другие ошибки из этой серии собраны на странице Ошибки компилятора Rust.
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0594]: cannot assign to `p.hp`, as `p` is not declared as mutable --> src/main.rs:8:5 |8 | p.hp = 5; // ❌ `p` is read-only | ^^^^^^^^ cannot assign |help: consider changing this to be mutable |7 | let mut p = Player { name: String::from("Ferris"), hp: 10 }; | +++Компилятор пишет: нельзя присвоить значение p.hp, потому что p не объявлена изменяемой, и советует сделать её изменяемой. Вот программа, которая выдаёт эту ошибку:
struct Player { name: String, hp: u32,}
fn main() { let p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; // ❌ `p` is read-only println!("{} has {} hp", p.name, p.hp);}Комментарий говорит: p доступна только для чтения. Ещё ты часто увидишь вторую формулировку: «cannot assign to *x, which is behind a & reference» (нельзя присвоить *x, оно за ссылкой &). Код ошибки тот же, причина немного другая: у тебя ссылка &, а ссылка & позволяет только смотреть. Ниже разобраны оба случая.
Что это значит
Заголовок раздела «Что это значит»Посмотри на ^^^^^^^^ под p.hp = 5 в строке 8. Ты меняешь поле p. Но p создана простым let, поэтому вся структура доступна только для чтения, включая каждое её поле.
Строка help: показывает исправление: let mut p. +++ отмечает три символа, которые нужно добавить.
Во второй формулировке компилятор указывает на строку вроде *x += 1 и добавляет `x` is a `&` reference, so it cannot be written to (x — ссылка &, поэтому писать через неё нельзя). Ты меняешь не свою переменную. Ты пытаешься изменить чужое значение через заимствованную ссылку, которая только для чтения.
Представь библиотечную книгу. Читать её можно сколько угодно, но писать в ней нельзя. Чтобы делать заметки, нужна своя копия (let mut) или разрешение владельца (&mut).
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»В Rust всё доступно только для чтения, пока ты не скажешь иначе. Чтобы переменную можно было менять, нужен let mut, а чтобы ссылка могла менять то, на что указывает, она должна быть &mut. Подробнее в уроках Структуры и Заимствование.
«Изменяемых полей» в Rust нет. Изменяемость принадлежит переменной (или ссылке), а не отдельным полям. Поэтому let mut p делает изменяемым каждое поле p, а простой let p — ни одного. Правило остаётся простым: посмотри, как значение объявлено, и сразу поймёшь, можно ли его менять.
Правило для ссылок защищает тебя от сюрпризов. Если ты передаёшь в функцию &player, её сигнатура обещает только читать. Без такого обещания любая функция могла бы тихо поменять твои данные, а компилятор не смог бы помешать тому, кто читает, и тому, кто пишет, трогать одно значение одновременно (об этом ошибка E0502).
Как исправить
Заголовок раздела «Как исправить»Исправление 1: добавь mut к переменной
Заголовок раздела «Исправление 1: добавь mut к переменной»Выбирай это, когда структура принадлежит тебе и ты хочешь менять её поля. Чаще всего это правильное исправление.
struct Player { name: String, hp: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), hp: 10 }; p.hp = 5; p.hp += 3; println!("У {} осталось {} hp", p.name, p.hp);}Вывод:
У Ferris осталось 8 hpС кортежами (pair.0 = 1;) и массивами (grid[2] = 7;) то же самое: переменной, которая их хранит, нужен mut. Для структуры внутри Vec строка team[0].hp = 10; требует let mut team; этот случай сообщается как E0596, но исправление то же.
Если ты присваиваешь всего один раз, mut может вовсе не понадобиться: сразу создай структуру с нужным значением, например Player { hp: 5, ..other }.
Исправление 2: перебирай через &mut v или iter_mut()
Заголовок раздела «Исправление 2: перебирай через &mut v или iter_mut()»Выбирай это, когда перебираешь коллекцию и хочешь изменить каждый элемент. for x in &v и v.iter() выдают ссылки &, а они только для чтения:
fn main() { let scores = vec![10, 20, 30]; for x in &scores { *x += 1; // ❌ `x` is a `&i32` } println!("{scores:?}");}error[E0594]: cannot assign to `*x`, which is behind a `&` reference --> src/main.rs:4:9 |3 | for x in &scores { | ------- this iterator yields `&` references4 | *x += 1; // ❌ `x` is a `&i32` | ^^^^^^^ `x` is a `&` reference, so it cannot be written to |help: use a mutable iterator instead |3 | for x in &mut scores { | +++Компилятор объясняет: этот итератор выдаёт ссылки &, и предлагает взять изменяемый итератор. Перебирай &mut scores (или вызови .iter_mut()) и сделай изменяемым сам вектор:
fn main() { let mut scores = vec![10, 20, 30]; for x in &mut scores { *x += 1; } println!("{scores:?}");
for x in scores.iter_mut() { *x *= 2; } println!("{scores:?}");}Вывод:
[11, 21, 31][22, 42, 62]&mut scores и scores.iter_mut() делают одно и то же; выбирай то, что читается лучше. Если менять список на месте не обязательно, можно собрать новый: let bigger: Vec<i32> = scores.iter().map(|x| x + 1).collect();. Подробнее в уроке Итераторы.
Исправление 3: возьми &mut self или параметр &mut
Заголовок раздела «Исправление 3: возьми &mut self или параметр &mut»Выбирай это, когда метод или функция меняют полученные данные. Метод с &self может только читать структуру, поэтому присваивание полю не проходит:
impl Account { fn deposit(&self, amount: u64) { self.balance += amount; // ❌ `self` is a `&` reference }}error[E0594]: cannot assign to `self.balance`, which is behind a `&` reference --> src/main.rs:7:9 |7 | self.balance += amount; | ^^^^^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so it cannot be written to |help: consider changing this to be a mutable reference |6 | fn deposit(&mut self, amount: u64) { | +++Здесь self — ссылка &, и rustc предлагает сделать её изменяемой. Обычная функция с параметром & даёт ту же ошибку: fn reset(score: &u32) { *score = 0; } падает с cannot assign to `*score`, which is behind a `&` reference. Исправление для обоих случаев одно: напиши &mut в сигнатуре, сделай переменную вызывающего кода let mut и передай &mut при вызове.
struct Account { balance: u64,}
impl Account { fn deposit(&mut self, amount: u64) { self.balance += amount; }}
fn reset(score: &mut u32) { *score = 0;}
fn main() { let mut acc = Account { balance: 0 }; acc.deposit(50); acc.deposit(25); println!("Баланс: {}", acc.balance);
let mut score = 42; reset(&mut score); println!("Счёт: {score}");}Вывод:
Баланс: 75Счёт: 0Для методов Rust сам подставляет &mut при вызове: acc.deposit(50) заимствует acc изменяемо, если acc объявлена через let mut. Для функций ты пишешь это сам: reset(&mut score). Если передать &score в функцию, которая ждёт &mut u32, получишь E0308 (types differ in mutability). Подробнее о методах — в уроке Структуры.
Исправление 4 (крайний случай): Cell или RefCell
Заголовок раздела «Исправление 4 (крайний случай): Cell или RefCell»Выбирай это, только когда получить &mut нельзя: например, ты реализуешь трейт, метод которого жёстко объявлен с &self, или многие части программы делят одно значение через Rc. Cell и RefCell позволяют менять значение через ссылку & («внутренняя изменяемость», interior mutability):
use std::cell::{Cell, RefCell};
struct Catalog { items: Vec<String>, lookups: Cell<u32>, history: RefCell<Vec<String>>,}
impl Catalog { fn contains(&self, name: &str) -> bool { self.lookups.set(self.lookups.get() + 1); self.history.borrow_mut().push(name.to_string()); self.items.iter().any(|item| item == name) }}
fn main() { let catalog = Catalog { items: vec![String::from("яблоко"), String::from("груша")], lookups: Cell::new(0), history: RefCell::new(Vec::new()), }; println!("груша? {}", catalog.contains("груша")); println!("киви? {}", catalog.contains("киви")); println!("Поисков: {}", catalog.lookups.get()); println!("История: {:?}", catalog.history.borrow());}Вывод:
груша? trueкиви? falseПоисков: 2История: ["груша", "киви"]Cell подходит для маленьких Copy-значений вроде счётчиков; RefCell — для всего остального. Но честно о цене: RefCell переносит проверку заимствований из времени компиляции во время выполнения и паникует, если заимствовать изменяемо дважды одновременно. Если работает обычный &mut, выбирай его. Между потоками вместо этого используй Mutex или атомарные типы (см. урок Общее состояние). Подробнее о RefCell — в уроке Умные указатели.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»Изменение значения внутри HashMap. *stock.get("apple").unwrap() += 1; падает с cannot assign to data in a `&` reference, потому что get возвращает ссылку &. Исправление: *stock.get_mut("apple").unwrap() += 1; или *stock.entry("apple").or_insert(0) += 1;. Подробнее в уроке HashMap.
Замыкание, переданное как Fn, меняет переменную. run(|| count += 1), где run принимает F: Fn(), падает с cannot assign to `count`, as it is a captured variable in a `Fn` closure. Исправление: смени ограничение на F: FnMut() (и напиши mut f внутри run). Подробнее в уроке Fn, FnMut и FnOnce.
Глобальный счётчик в static. static VISITS: u32 = 0;, а потом VISITS += 1; падает с cannot assign to immutable static item `VISITS`. Исправление: используй static VISITS: AtomicU32 = AtomicU32::new(0); и VISITS.fetch_add(1, Ordering::Relaxed); или, что лучше, передавай счётчик как &mut.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0596: cannot borrow as mutable. Тот же пропущенный
mut, но для вызова метода вродеlist.push(x), а не для присваивания. - E0384: cannot assign twice to immutable variable. Ты присваиваешь целой переменной (
x = 5;), а не полю и не через ссылку. - E0308: mismatched types,
types differ in mutability— когда передаёшь&xтам, где ожидается&mut x. - E0502: доступ
&mutу тебя есть, но значение ещё кто-то читает. - E0507: cannot move out of a value behind a reference. Снова правило «только для чтения», но на этот раз для перемещений, а не для записи.
- Все ошибки компилятора: полный список страниц с ошибками.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. let user = User { age: 30 }; user.age = 31; даёт E0594. Как исправить?
2. for p in &prices { *p += 1; } не компилируется. Что поменять?
3. Метод fn rename(&self, n: String) { self.name = n; } даёт E0594. Как это обычно исправляют?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0594 — список кодов ошибок Rust (на английском): официальное объяснение этой ошибки.
- Определение и создание экземпляров структур — книга «Язык программирования Rust» (перевод на русский): почему изменяемым должен быть весь экземпляр, а не отдельные поля.
- Ссылки и заимствование — книга «Язык программирования Rust» (перевод на русский): часть про изменяемые ссылки объясняет разницу между
&и&mut. - Синтаксис метода — книга «Язык программирования Rust» (перевод на русский): когда методу нужен
&self,&mut selfилиself. - RefCell и внутренняя изменяемость — книга «Язык программирования Rust» (перевод на русский): как
RefCellпозволяет менять значение через&и во что это обходится во время выполнения.