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

Ошибка 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 here
5 | let withdraw = &mut balance;
| ^^^^^^^^^^^^ second mutable borrow occurs here
6 |
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}");
}

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

  1. Строка 4: deposit становится изменяемым заимствованием balance.
  2. Строка 5: withdraw пытается стать вторым изменяемым заимствованием balance.
  3. Строка 7: deposit используется после строки 5. Значит, первое заимствование всё ещё живо, когда начинается второе.

Третья пометка — самая важная. Два заимствования &mut — проблема, только когда их времена жизни пересекаются. Если бы работа с deposit закончилась до строки 5, ошибки бы не было.

Представь общий документ с одним ключом для редактирования. Кто держит ключ, тот и может менять документ. Копию ключа 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] длится всего одну строку.

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 всё время, пока живут. Исправление: используй одно замыкание или вызови первое в последний раз до создания второго. См. Замыкания.

Проверь себя

1. Когда Rust сообщает об E0499?

2. Ты создаёшь let a = &mut v;, в последний раз используешь a, а потом создаёшь let b = &mut v;. Что произойдёт?

3. Какой инструмент позволяет получить две ссылки &mut на разные элементы одного среза?

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