Ошибка 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 here4 | v.push(4); // ❌ меняет Vec, пока `first` жив | ^^^^^^^^^ mutable borrow occurs here5 | 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` используется здесь}Что это значит
Заголовок раздела «Что это значит»Компилятор указывает на три строки, и чтобы понять ошибку, нужны все три:
- Строка 3:
&v[0]создаёт ссылку только для чтения внутрьv. Это неизменяемое заимствование. - Строка 4:
v.push(4)нужно изменитьv, поэтому он берёт изменяемое заимствование. - Строка 5:
firstиспользуется снова. Именно эта строка держит первое заимствование живым на протяжении строки 4.
Без строки 5 код бы скомпилировался. Проблема не в том, что существуют оба заимствования, а в том, что они пересекаются.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Правило такое: много читателей или один писатель, но никогда одновременно. Ты его уже встречал в уроке Заимствование.
Для Vec есть очень конкретная причина. Vec хранит элементы в одном блоке памяти. Когда ты делаешь push, а блок уже заполнен, Vec переносит всё в новый, больший блок и освобождает старый. Любая ссылка в старый блок (как first) теперь указывает на освобождённую память. В C++ это знаменитый баг «инвалидации итераторов», который может уронить программу или тихо прочитать мусор.
Это как записать адрес друга, а потом он переезжает в дом побольше. Записка выглядит нормально, но указывает на пустое здание. Rust отказывается компилировать такой код, вместо того чтобы отправить тебя в пустое здание.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: дочитай, прежде чем писать
Заголовок раздела «Исправление 1: дочитай, прежде чем писать»Начни с этого варианта. Начиная с 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]Исправление 2: скопируй значение
Заголовок раздела «Исправление 2: скопируй значение»Выбирай этот вариант, когда старое значение нужно после изменения. Возьми копию вместо ссылки. Для чисел просто убери &. Для 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 стоит немного памяти, зато код понятный и очевидно правильный. Обычно это хорошая сделка.
Исправление 5: используй индексы
Заголовок раздела «Исправление 5: используй индексы»Это крайний вариант, когда остальные не подходят. Индекс — просто число, а не заимствование, поэтому он не мешает изменениям:
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
Что почитать
Заголовок раздела «Что почитать»- E0502 — Rust error codes index (на английском): официальное объяснение этой ошибки.
- Хранение списков значений с векторами — книга «Язык программирования Rust» (перевод на русский): там есть ровно этот пример
let first = &v[0]; v.push(..)и объяснение, почему он не работает. - Ссылки и заимствование — книга «Язык программирования Rust» (перевод на русский): правило «много читателей или один писатель» целиком.
- Non-lexical lifetimes — The Edition Guide (на английском): почему заимствование теперь заканчивается на последнем использовании, а не на закрывающей скобке.
Vec— документация стандартной библиотеки (на английском):retain,iter_mut,extendи другие методы, которые позволяют безопасно менять вектор.