Ошибка 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 так делает
Заголовок раздела «Почему Rust так делает»В Rust переменные по умолчанию неизменяемые, а ссылки по умолчанию только для чтения. Чтобы что-то менять, нужно явное согласие: let mut для переменной, которой ты владеешь, &mut для ссылки на то, что ты заимствовал. См. Переменные и Заимствование.
Так коду проще доверять. Если функция принимает &Vec<String>, уже по сигнатуре ясно, что твой список она не изменит. Если принимает &mut Vec<String> — может изменить. Ничего не меняется у тебя за спиной.
То же правило делает возможными более серьёзные гарантии безопасности Rust. Раз «может меняться» всегда записано явно, компилятор может проследить, чтобы писатель и читатель никогда не работали одновременно (это E0502 и E0499). E0596 — первый шаг: сначала нужно попросить права на запись, а уже потом кто-то проверит, безопасна ли запись.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: добавь mut к переменной
Заголовок раздела «Исправление 1: добавь mut к переменной»Выбирай этот вариант, когда ты владеешь значением и оно должно меняться: 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 mutable4 | click(); // ❌ нельзя изменяемо заимствовать `click` | ----- cannot borrow as mutable5 | 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
Что почитать
Заголовок раздела «Что почитать»- E0596 — Rust error codes index (на английском): официальное объяснение этой ошибки.
- Ссылки и заимствование — книга «Язык программирования Rust» (перевод на русский): раздел «Изменяемые ссылки» объясняет
&mutи почему его нужно просить явно. - Синтаксис метода — книга «Язык программирования Rust» (перевод на русский):
&selfпротив&mut selfи что каждому из них нужно от вызывающего кода. - Замыкания — книга «Язык программирования Rust» (перевод на русский): как замыкания захватывают переменные и почему изменяющее замыкание должно быть
mut. - Изменяемость (заимствование) — Rust на примерах (перевод на русский): примеры заимствований
&и&mutрядом, которые можно запустить.