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

Ошибка Rust E0502: cannot borrow as mutable because it is also borrowed as immutable

E0502 значит, что ты попытался изменить значение, пока другая часть кода ещё читает его через ссылку. С векторами эта ошибка встречается постоянно, а исправление обычно сводится к тому, чтобы переставить одну строку или скопировать одно значение. Сначала на этой странице показано исправление, а потом объяснение, почему Rust на этом настаивает. Другие ошибки из этой серии собраны на странице Ошибки компилятора Rust.

error[E0502]: cannot borrow `v` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let first = &v[0]; // ссылка внутрь Vec
| - immutable borrow occurs here
4 | v.push(4); // ❌ меняет Vec, пока `first` жив
| ^^^^^^^^^ mutable borrow occurs here
5 | println!("Первый: {first}"); // ...потому что `first` используется здесь
| ----- immutable borrow later used here

Компилятор говорит: нельзя заимствовать v как изменяемое, потому что оно уже заимствовано как неизменяемое. Пометки показывают, где возникло неизменяемое заимствование (immutable borrow), где изменяемое (mutable borrow) и где неизменяемое используется позже.

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

fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0]; // ссылка внутрь Vec
v.push(4); // ❌ меняет Vec, пока `first` жив
println!("Первый: {first}"); // ...потому что `first` используется здесь
}

Компилятор указывает на три строки, и чтобы понять ошибку, нужны все три:

  1. Строка 3: &v[0] создаёт ссылку только для чтения внутрь v. Это неизменяемое заимствование.
  2. Строка 4: v.push(4) нужно изменить v, поэтому он берёт изменяемое заимствование.
  3. Строка 5: first используется снова. Именно эта строка держит первое заимствование живым на протяжении строки 4.

Без строки 5 код бы скомпилировался. Проблема не в том, что существуют оба заимствования, а в том, что они пересекаются.

Правило такое: много читателей или один писатель, но никогда одновременно. Ты его уже встречал в уроке Заимствование.

Для Vec есть очень конкретная причина. Vec хранит элементы в одном блоке памяти. Когда ты делаешь push, а блок уже заполнен, Vec переносит всё в новый, больший блок и освобождает старый. Любая ссылка в старый блок (как first) теперь указывает на освобождённую память. В C++ это знаменитый баг «инвалидации итераторов», который может уронить программу или тихо прочитать мусор.

Это как записать адрес друга, а потом он переезжает в дом побольше. Записка выглядит нормально, но указывает на пустое здание. Rust отказывается компилировать такой код, вместо того чтобы отправить тебя в пустое здание.

Начни с этого варианта. Начиная с Rust 2018 заимствование заканчивается на последнем использовании, а не на закрывающей фигурной скобке. Это называется нелексическими временами жизни (non-lexical lifetimes). Так что просто закончи пользоваться ссылкой до того, как меняешь вектор:

fn main() {
let mut v = vec![1, 2, 3];
let first = &v[0];
println!("Первый: {first}"); // последнее использование `first`: заимствование кончается здесь
v.push(4); // ✅ больше никто не читает
println!("Теперь: {:?}", v);
}

Вывод:

Первый: 1
Теперь: [1, 2, 3, 4]
▶ Попробуй сам

Выбирай этот вариант, когда старое значение нужно после изменения. Возьми копию вместо ссылки. Для чисел просто убери &. Для String используй .clone():

fn main() {
let mut queue = vec![String::from("deploy"), String::from("test")];
let next = queue[0].clone(); // своя копия, а не ссылка
queue.push(String::from("release"));
println!("Следующий: {next}");
println!("Очередь: {:?}", queue);
}

Вывод:

Следующий: deploy
Очередь: ["deploy", "test", "release"]
▶ Попробуй сам

Если написать let first = v[0]; для Vec<i32>, копия получится бесплатно, потому что i32 реализует Copy.

Исправление 3: меняй элементы на месте через iter_mut или retain

Заголовок раздела «Исправление 3: меняй элементы на месте через iter_mut или retain»

Выбирай этот вариант, если ты написал цикл вроде for p in prices.iter(), а внутри попробовал вызвать prices.remove(i) или изменить элементы. Это тоже даёт E0502. В стандартной библиотеке есть методы, сделанные как раз для этого:

fn main() {
let mut prices = vec![120, 45, 300, 80];
// меняем каждый элемент на месте
for p in prices.iter_mut() {
*p = *p * 9 / 10; // скидка 10%
}
// удаляем ненужные элементы
prices.retain(|&p| p >= 50);
println!("{:?}", prices);
}

Вывод:

[108, 270, 72]
▶ Попробуй сам

iter_mut даёт по одной &mut на каждый элемент, так что писатель всегда один. retain сам удаляет элементы — безопасно и за один проход. См. Векторы и Итераторы.

Исправление 4: сначала собери изменения, потом применяй

Заголовок раздела «Исправление 4: сначала собери изменения, потом применяй»

Выбирай этот вариант, когда нужно добавить элементы на основе прочитанного. Сначала прочитай и собери, потом запиши — отдельным шагом:

fn main() {
let mut scores = vec![10, 25, 7, 40];
let bonuses: Vec<i32> = scores.iter().filter(|&&s| s > 20).map(|s| s * 2).collect();
scores.extend(bonuses); // заимствование для чтения закончилось, можно писать
println!("{:?}", scores);
}

Вывод:

[10, 25, 7, 40, 50, 80]
▶ Попробуй сам

Лишний Vec стоит немного памяти, зато код понятный и очевидно правильный. Обычно это хорошая сделка.

Это крайний вариант, когда остальные не подходят. Индекс — просто число, а не заимствование, поэтому он не мешает изменениям:

fn main() {
let mut scores = vec![10, 25, 7];
for i in 0..scores.len() {
if scores[i] > 20 {
scores.push(scores[i] * 2);
}
}
println!("{:?}", scores);
}

Вывод:

[10, 25, 7, 50]
▶ Попробуй сам

0..scores.len() вычисляется один раз, до начала цикла, поэтому новые элементы цикл не посещает. Осторожнее, когда удаляешь элементы по индексу: позиции сдвигаются, и можно пропустить элементы или получить панику. Для удаления безопаснее retain (исправление 3).

push в Vec во время цикла по нему. for s in &scores { scores.push(..) } заимствует scores на весь цикл. Исправление: сначала собери, а потом сделай extend (исправление 4), или перебирай по индексу (исправление 5).

Вызов метода с &mut self во время цикла по полю. Внутри блоков impl такое бывает очень часто:

fn audit(&mut self) {
for item in &self.items {
self.record(item); // ❌ cannot borrow `*self` as mutable because it is also borrowed as immutable
}
}

self.record изменяемо заимствует весь self, включая self.items. Исправление: обращайся к другому полю напрямую (self.log.push(item.clone())), потому что Rust разрешает одновременно заимствовать два разных поля. Или сначала склонируй элементы. См. Структуры.

Значение из HashMap хранится, пока ты вставляешь новое. let apples = stock.get("apples").unwrap();, затем stock.insert(..), а потом использование apples дают E0502. Исправление: скопируй значение — let apples = *stock.get("apples").unwrap(); или .copied(). См. HashMap.

  • E0499: два изменяемых заимствования одновременно. Та же идея, только два писателя вместо читателя и писателя.
  • E0382: ты переместил значение, а потом использовал его. Часто появляется, когда исправляешь E0502 перемещением вместо заимствования.
  • E0106: не указано время жизни у функции, которая возвращает ссылку.
  • Все ошибки компилятора: полный список страниц об ошибках.

Проверь себя

1. Почему не компилируется let first = &v[0]; v.push(4); println!("{first}");?

2. Нужно удалить из Vec<i32> все элементы меньше 50. Какое исправление E0502 самое чистое?

3. Когда заканчивается неизменяемое заимствование в современном Rust?

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