Ошибка Rust E0499: cannot borrow as mutable more than once at a time
Ошибка E0499 значит, что ты попытался получить две изменяемые ссылки (&mut) на одно и то же значение одновременно. Это одна из самых частых ошибок анализатора заимствований (borrow checker), и исправление обычно маленькое: часто достаточно переставить одну строку. (Ищешь другую ошибку? Загляни в обзор ошибок компилятора.)
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0499]: cannot borrow `balance` as mutable more than once at a time --> src/main.rs:5:20 |4 | let deposit = &mut balance; | ------------ first mutable borrow occurs here5 | let withdraw = &mut balance; | ^^^^^^^^^^^^ second mutable borrow occurs here6 |7 | *deposit += 50; | -------------- first borrow later used hereКомпилятор говорит: нельзя заимствовать balance как изменяемое больше одного раза одновременно. Пометки показывают первое изменяемое заимствование, второе и место, где первое используется позже.
Вот программа, которая выдаёт эту ошибку:
fn main() { let mut balance = 100;
let deposit = &mut balance; let withdraw = &mut balance;
*deposit += 50; *withdraw -= 30;
println!("Баланс: {balance}");}Что это значит
Заголовок раздела «Что это значит»Компилятор указывает на три места, и вместе они рассказывают всю историю:
- Строка 4:
depositстановится изменяемым заимствованиемbalance. - Строка 5:
withdrawпытается стать вторым изменяемым заимствованиемbalance. - Строка 7:
depositиспользуется после строки 5. Значит, первое заимствование всё ещё живо, когда начинается второе.
Третья пометка — самая важная. Два заимствования &mut — проблема, только когда их времена жизни пересекаются. Если бы работа с deposit закончилась до строки 5, ошибки бы не было.
Представь общий документ с одним ключом для редактирования. Кто держит ключ, тот и может менять документ. Копию ключа Rust не сделает: второму придётся подождать, пока первый его вернёт.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Правило заимствования в Rust такое: в любой момент может быть много читателей (&) или один писатель (&mut), но никогда больше одного писателя. Ссылка &mut — это обещание: «пока я существую, никто другой не трогает это значение».
Это обещание предотвращает настоящие баги. Если бы два куска кода могли менять одно значение одновременно, один мог бы изменить размер Vec, пока другой всё ещё указывает в его старую память. В других языках это падение программы или тихая порча данных. А между потоками — гонка данных (data race).
Благодаря этому правилу компилятор уверен, что, пока &mut жива, это единственный путь к данным. Такую безопасность ты получаешь без затрат во время выполнения. Цена в том, что иногда приходится яснее объяснить компилятору, что твои два заимствования на самом деле не конфликтуют.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: закончи первое заимствование до начала второго
Заголовок раздела «Исправление 1: закончи первое заимствование до начала второго»Начни с этого варианта. Он исправляет большинство ошибок E0499. Заимствование длится до своего последнего использования, а не до конца блока. Так что сделай всю работу с первой ссылкой, а потом создай вторую:
fn main() { let mut balance = 100;
let deposit = &mut balance; *deposit += 50; // последнее использование `deposit`: заимствование кончается здесь
let withdraw = &mut balance; // ✅ сейчас никто больше не заимствует *withdraw -= 30;
println!("Баланс: {balance}");}Вывод:
Баланс: 120Часто лишние переменные вообще не нужны: balance += 50;, а затем balance -= 30; работают точно так же.
Исправление 2: заимствуй части, которые не пересекаются
Заголовок раздела «Исправление 2: заимствуй части, которые не пересекаются»Иногда тебе действительно нужны две &mut одновременно, но они указывают на разные части данных. Компилятор не всегда это видит, поэтому ты ему показываешь.
Для двух элементов Vec или среза и tasks[0], и tasks[1] заимствуют весь tasks. Используй split_at_mut (на английском), чтобы разрезать его на две половины, которые гарантированно не пересекаются:
fn main() { let mut tasks = vec![String::from("write"), String::from("test")];
// Делим Vec на две непересекающиеся половины let (left, right) = tasks.split_at_mut(1); let first = &mut left[0]; let second = &mut right[0];
std::mem::swap(first, second); println!("{tasks:?}");}Вывод:
["test", "write"](Если нужно именно поменять элементы местами, ещё проще tasks.swap(0, 1). split_at_mut нужен, когда обе ссылки нужны для настоящей работы.)
Со структурой та же ошибка появляется, когда вызываешь два метода, каждый из которых принимает &mut self, например p.name_mut() и p.score_mut(). Каждый вызов заимствует всю структуру. Если вместо этого заимствовать поля напрямую, компилятор увидит, что они раздельные:
struct Player { name: String, score: u32,}
fn main() { let mut p = Player { name: String::from("Ferris"), score: 0 };
// Заимствуем поля напрямую, а не через методы let name = &mut p.name; let score = &mut p.score;
name.push_str(" (краб)"); *score += 10; println!("{} набрал {} очков", p.name, p.score);}Вывод:
Ferris (краб) набрал 10 очковИсправление 3: используй индексы, а не держи ссылки
Заголовок раздела «Исправление 3: используй индексы, а не держи ссылки»Классика: ты перебираешь Vec через iter_mut() и внутри цикла пытаешься сделать push в него же. Итератор держит &mut весь цикл, так что push — вторая такая ссылка:
fn main() { let mut queue = vec![1, 2, 3]; for job in queue.iter_mut() { *job *= 10; queue.push(*job); // ❌ E0499: `queue` уже заимствован через iter_mut() }}Когда цикл должен менять саму коллекцию, перебирай позиции. Индекс — просто число, он ничего не заимствует:
fn main() { let mut queue = vec![1, 2, 3];
// Перебираем позиции, а не держим ссылку внутрь `queue` for i in 0..queue.len() { queue[i] *= 10; let job = queue[i]; queue.push(job); }
println!("{queue:?}");}Вывод:
[10, 20, 30, 10, 20, 30]0..queue.len() вычисляется один раз, до начала цикла, поэтому новые элементы цикл не посещает. Каждое заимствование queue[i] длится всего одну строку.
Исправление 4: RefCell (крайний вариант)
Заголовок раздела «Исправление 4: RefCell (крайний вариант)»RefCell переносит проверку заимствований со времени компиляции на время выполнения. Можно вызывать borrow_mut() без переменной с mut, лишь бы два изменяемых заимствования не жили в один и тот же момент:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100); // обрати внимание: `mut` не нужен
*balance.borrow_mut() += 50; // заимствовали, изменили, заимствование кончается на `;` *balance.borrow_mut() -= 30;
println!("Баланс: {}", balance.borrow());}Вывод:
Баланс: 120Будем честны: RefCell не отменяет правило, он просто проверяет его позже. Если одновременно держать живыми два стража borrow_mut(), программа скомпилируется, но запаникует при запуске:
use std::cell::RefCell;
fn main() { let balance = RefCell::new(100);
let mut deposit = balance.borrow_mut(); let mut withdraw = balance.borrow_mut(); // 💥 `deposit` всё ещё жив
*deposit += 50; *withdraw -= 30;}thread 'main' (12345) panicked at src/main.rs:7:32:RefCell already borrowedПоток main запаниковал в строке 7 с сообщением «RefCell уже заимствован». Так что бери RefCell, только когда данные действительно общие (например, внутри Rc<RefCell<T>>), а исправления 1–3 не подходят. Это не способ заставить компилятор замолчать.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»&mutсохраняются в цикле.handles.push(&mut counter)внутри циклаforпадает с сообщением «mutably borrowed here in the previous iteration of the loop» («изменяемо заимствовано здесь на предыдущей итерации цикла»). Каждый проход цикла добавляет ещё одну&mutна то же значение. Исправление: храни индексы или владеющие значения, либо меняй значение прямо в цикле.- Два вызова
get_mutуHashMap.let a = stock.get_mut("apples"), а потомlet p = stock.get_mut("pears")заимствуютstockдважды. Исправление: закончи работу сa, прежде чем снова вызыватьget_mut, или используйstock.get_disjoint_mut(["apples", "pears"]), чтобы получить оба сразу. - Два замыкания, которые меняют одну переменную.
let mut inc = || count += 1;иlet mut inc2 = || count += 2;держат&mut countвсё время, пока живут. Исправление: используй одно замыкание или вызови первое в последний раз до создания второго. См. Замыкания.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0502: cannot borrow as mutable because it is also borrowed as immutable: та же идея, но одно из заимствований —
&только для чтения. - E0382: borrow of moved value: ты использовал значение после того, как его куда-то переместил.
- E0106: missing lifetime specifier: Rust должен знать, как долго живёт возвращаемая ссылка.
- Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Когда Rust сообщает об E0499?
2. Ты создаёшь let a = &mut v;, в последний раз используешь a, а потом создаёшь let b = &mut v;. Что произойдёт?
3. Какой инструмент позволяет получить две ссылки &mut на разные элементы одного среза?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0499 — Rust error codes index (на английском): официальное объяснение этой ошибки с минимальным примером.
- Ссылки и заимствование — книга «Язык программирования Rust» (перевод на русский): глава, которая вводит правило «только одна
&mutв каждый момент». - Splitting Borrows — The Rustonomicon (на английском): почему поля структуры можно заимствовать по отдельности, а элементы среза — нет, и как работает
split_at_mut. RefCell<T>и внутренняя изменяемость — книга «Язык программирования Rust» (перевод на русский): когда перенос проверки заимствований на время выполнения — правильное решение.