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

Ошибка Rust E0596: cannot borrow as mutable, as it is not declared as mutable

E0596 значит, что какой-то код хочет изменить значение (ему нужно изменяемое заимствование, &mut), а ты дал ему право только читать. Обычно ты забыл mut у let или функция принимает & там, где ей нужен &mut. Ошибка очень частая, и строка help: компилятора почти всегда точно говорит, куда добавить mut. Сначала на этой странице показано исправление, а потом объяснение. Другие ошибки из этой серии собраны на странице Ошибки компилятора Rust.

error[E0596]: cannot borrow `tasks` as mutable, as it is not declared as mutable
--> src/main.rs:3:5
|
3 | tasks.push("написать тесты"); // ❌ push должен изменить `tasks`
| ^^^^^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
2 | let mut tasks = Vec::new();
| +++

Компилятор пишет: нельзя заимствовать tasks как изменяемое, потому что переменная не объявлена изменяемой. Подсказка предлагает сделать её изменяемой.

Вот программа, которая выдаёт эту ошибку:

fn main() {
let tasks = Vec::new();
tasks.push("написать тесты"); // ❌ push должен изменить `tasks`
println!("{tasks:?}");
}

Встречается и вторая формулировка: «cannot borrow *tasks as mutable, as it is behind a & reference» («нельзя изменяемо заимствовать *tasks, потому что он за ссылкой &»). Код ошибки тот же, причина немного другая: переменная — это ссылка &, поэтому менять то, на что она указывает, нельзя. Ниже разобраны оба случая.

Посмотри на ^^^^^ под tasks в строке 3. Чтобы добавить элемент, Vec::push нужно изменяемое заимствование вектора: его сигнатура — fn push(&mut self, value: T). Но tasks создан обычным let, поэтому он только для чтения. Rust не может выдать &mut на то, про что ты сказал, что оно никогда не изменится.

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

Представь общий документ с доступом «только просмотр». Ты можешь прочитать каждое слово, но кнопка «Редактировать» серая. mut (или &mut) — это способ попросить доступ на редактирование.

В Rust переменные по умолчанию неизменяемые, а ссылки по умолчанию только для чтения. Чтобы что-то менять, нужно явное согласие: let mut для переменной, которой ты владеешь, &mut для ссылки на то, что ты заимствовал. См. Переменные и Заимствование.

Так коду проще доверять. Если функция принимает &Vec<String>, уже по сигнатуре ясно, что твой список она не изменит. Если принимает &mut Vec<String> — может изменить. Ничего не меняется у тебя за спиной.

То же правило делает возможными более серьёзные гарантии безопасности Rust. Раз «может меняться» всегда записано явно, компилятор может проследить, чтобы писатель и читатель никогда не работали одновременно (это E0502 и E0499). E0596 — первый шаг: сначала нужно попросить права на запись, а уже потом кто-то проверит, безопасна ли запись.

Выбирай этот вариант, когда ты владеешь значением и оно должно меняться: Vec, в который ты делаешь push, String, к которой ты что-то дописываешь, или структура, у которой ты вызываешь метод с &mut self. Чаще всего правильное исправление именно это.

struct Counter {
count: u32,
}
impl Counter {
fn increment(&mut self) {
self.count += 1;
}
}
fn main() {
let mut tasks = Vec::new();
tasks.push("написать тесты");
tasks.push("исправить баг");
println!("{tasks:?}");
let mut counter = Counter { count: 0 };
counter.increment();
counter.increment();
println!("Счётчик: {}", counter.count);
}

Вывод:

["написать тесты", "исправить баг"]
Счётчик: 2
▶ Попробуй сам

Без mut у counter вызов counter.increment() падает с cannot borrow counter as mutable. Вызов метода с &mut self изменяемо заимствует всю переменную, поэтому она должна быть mut. Методы, которые принимают только &self, прекрасно работают и без него. См. Добавляем поведение: методы.

То же самое с параметром, которым владеет функция: fn shout(s: String) не может вызвать s.push('!'), а fn shout(mut s: String) может.

Исправление 2: сделай параметр &mut и передавай &mut x

Заголовок раздела «Исправление 2: сделай параметр &mut и передавай &mut x»

Выбирай этот вариант, когда функции нужно изменить то, чем владеет вызывающий код. Эта версия даёт формулировку «behind a & reference»:

fn add_task(tasks: &Vec<String>, task: &str) {
tasks.push(task.to_string()); // ❌ `tasks` — это ссылка `&`
}
error[E0596]: cannot borrow `*tasks` as mutable, as it is behind a `&` reference
--> src/main.rs:2:5
|
2 | tasks.push(task.to_string()); // ❌ `tasks` — это ссылка `&`
| ^^^^^ `tasks` is a `&` reference, so it cannot be borrowed as mutable
|
help: consider changing this to be a mutable reference
|
1 | fn add_task(tasks: &mut Vec<String>, task: &str) {
| +++

Компилятор объясняет: tasks — ссылка &, поэтому её нельзя заимствовать как изменяемую, — и предлагает сделать её изменяемой ссылкой. Исправлять нужно в трёх местах: тип параметра становится &mut Vec<String>, переменная вызывающего кода — let mut, а при вызове передаётся &mut tasks:

fn add_task(tasks: &mut Vec<String>, task: &str) {
tasks.push(task.to_string());
}
fn main() {
let mut tasks = Vec::new();
add_task(&mut tasks, "написать тесты");
add_task(&mut tasks, "обновить документацию");
println!("Задач: {} — {tasks:?}", tasks.len());
}

Вывод:

Задач: 2 — ["написать тесты", "обновить документацию"]
▶ Попробуй сам

Если поменять параметр, но по-прежнему передавать &tasks, получишь вместо этого E0308 с заметкой types differ in mutability («типы различаются изменяемостью»). Это значит лишь одно: напиши &mut tasks и в месте вызова. &mut при вызове пишется нарочно: любой, кто читает main, видит, что add_task может изменить tasks.

Исправление 3: сделай привязку замыкания mut

Заголовок раздела «Исправление 3: сделай привязку замыкания mut»

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

fn main() {
let mut clicks = 0;
let click = || clicks += 1;
click(); // ❌ нельзя изменяемо заимствовать `click`
click();
println!("Кликов: {clicks}");
}
error[E0596]: cannot borrow `click` as mutable, as it is not declared as mutable
--> src/main.rs:3:9
|
3 | let click = || clicks += 1;
| ^^^^^ ------ calling `click` requires mutable binding due to mutable borrow of `clicks`
| |
| not mutable
4 | click(); // ❌ нельзя изменяемо заимствовать `click`
| ----- cannot borrow as mutable
5 | click();
| ----- cannot borrow as mutable
|
help: consider changing this to be mutable
|
3 | let mut click = || clicks += 1;
| +++

Пометка поясняет: чтобы вызывать click, нужна изменяемая привязка, потому что замыкание изменяемо заимствует clicks. Сама переменная clicks уже mut. Не хватает mut у замыкания:

fn main() {
let mut clicks = 0;
let mut click = || clicks += 1;
click();
click();
click();
println!("Кликов: {clicks}");
}

Вывод:

Кликов: 3
▶ Попробуй сам

Многих это удивляет, ведь замыкание «не выглядит» изменяющимся. Но внутри оно держит &mut clicks, поэтому это замыкание FnMut, а для вызова FnMut нужен mut. См. Замыкания, которые меняют захваченные переменные и Fn, FnMut и FnOnce.

Исправление 4: используй iter_mut() или &mut в цикле

Заголовок раздела «Исправление 4: используй iter_mut() или &mut в цикле»

Выбирай этот вариант, когда перебираешь коллекцию и хочешь изменить каждый элемент. .iter() и for x in &list выдают ссылки &, а они только для чтения:

for name in names.iter() {
name.push('!'); // ❌ cannot borrow `*name` as mutable, as it is behind a `&` reference
}

Перейди на .iter_mut() или &mut names (и сделай сам names изменяемым):

fn main() {
let mut names = vec![String::from("ferris"), String::from("corro")];
for name in names.iter_mut() {
name.push('!');
}
println!("{names:?}");
let mut prices = vec![10, 20, 30];
for price in &mut prices {
*price += 5;
}
println!("{prices:?}");
}

Вывод:

["ferris!", "corro!"]
[15, 25, 35]
▶ Попробуй сам

Компилятор тоже это предлагает: help: use mutable method ... names.iter_mut(). То же относится к замыканиям в цепочках итераторов: words.iter().for_each(|w| w.push('!')) не работает, а words.iter_mut().for_each(|w| w.push('!')) работает. См. Итераторы.

Vec или String собираются по шагам. let list = Vec::new();, а потом list.push(...), или let s = String::new();, а потом s.push_str(...). Исправление: let mut. Если все элементы известны заранее, let list = vec![a, b, c]; вообще не нуждается в mut.

Вызов метода-«сеттера» или метода обновления у структуры. account.deposit(50), где deposit принимает &mut self, падает, если ты не написал let mut account. Исправление: let mut account = Account::new();. См. Структуры.

Вспомогательные функции, которые по привычке принимают &. Ты пишешь fn reset(board: &Board) и пытаешься изменить board внутри. Исправление: замени на board: &mut Board и вызывай reset(&mut board). Хорошая привычка: пиши &, когда функция только читает, и &mut, когда она меняет.

Изменение значения внутри HashMap. if let Some(v) = map.get("key") { v.push(..) } падает, потому что get возвращает ссылку &. Исправление: используй map.get_mut("key") или API entry. См. HashMap.

  • E0384: cannot assign twice to immutable variable. Тот же забытый mut, но для x = ... или x += 1, а не для вызова метода.
  • E0594: нельзя присвоить значение полю или через ссылку & — например, p.hp = 5;, когда p не mut, или *x += 1 в for x in &v. Исправления те же: let mut или &mut.
  • E0308: mismatched types, types differ in mutability — когда передаёшь &x в функцию, которой нужен &mut x.
  • E0502: ты получил доступ &mut, но значение всё ещё кто-то читает.
  • E0499: два заимствования &mut одного значения одновременно.
  • Все ошибки компилятора: полный список страниц об ошибках.

Проверь себя

1. let scores = Vec::new(); scores.push(10); даёт E0596. Как исправить?

2. Функция fn add(list: &Vec<u32>) вызывает list.push(1). Что поменять?

3. let mut total = 0; let add = |x| total += x; add(5); не компилируется. Почему?

Результат: 0 / 3