Владение и заимствование в Rust: подробное объяснение
Продвинутое3 минРедакция Innovialabs
Коротко
У каждого значения в Rust один владелец; его можно переместить или одолжить — много раз на чтение или один раз на изменение — и ссылка не может пережить само значение.
Три правила, на которых всё держится
Rust управляет памятью без сборщика мусора благодаря владению. Правила такие:
- У каждого значения есть ровно один владелец — переменная.
- Когда владелец выходит из области видимости, значение освобождается.
- Значение можно одолжить по ссылке: либо сколько угодно неизменяемых ссылок
&T, либо одну изменяемую&mut T— но не одновременно.
Компилятор проверяет это на этапе сборки. Почти все «непонятные» ошибки новичков — нарушение одного из этих правил. Разберём их по кодам ошибок.
E0382: использование перемещённого значения
let s = String::from("привет");
let t = s;
println!("{}", s); // error[E0382]: borrow of moved value: `s`
String хранит данные в куче, и присваивание перемещает владение в t. Переменная s больше недействительна.
Как исправить:
- Нужна копия —
let t = s.clone();. Это явное и потенциально дорогое копирование. - Нужен только доступ — одолжите:
let t = &s;. - Передаёте в функцию — принимайте
&strили&StringвместоString.
Простые типы вроде i32, bool, char реализуют Copy и копируются автоматически, поэтому с ними ошибки нет.
E0502: одновременно изменяемое и неизменяемое заимствование
let mut v = vec![1, 2, 3];
let first = &v[0];
v.push(4); // error[E0502]
println!("{}", first);
push может перевыделить память вектора, и first указала бы в никуда. Rust это запрещает.
Как исправить: закончите пользоваться ссылкой до изменения, либо скопируйте значение:
let first = v[0]; // i32 копируется
v.push(4);
Важно: заимствование длится до последнего использования ссылки, а не до конца блока. Поэтому часто достаточно переставить строки.
E0499: две изменяемые ссылки сразу
let mut s = String::new();
let a = &mut s;
let b = &mut s; // error[E0499]
a.push('x');
Одна изменяемая ссылка гарантирует отсутствие гонок данных. Решения:
- Используйте ссылки по очереди, ограничив их отдельными блоками
{ }. - Для разных полей структуры заимствуйте поля, а не всю структуру.
- Для разных частей среза —
split_at_mut.
E0106 и E0597: времена жизни
fn longest(a: &str, b: &str) -> &str { // error[E0106]
if a.len() > b.len() { a } else { b }
}
Компилятор не знает, к какому аргументу привязан результат. Укажите время жизни явно:
fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() > b.len() { a } else { b }
}
'a не меняет, сколько живут данные. Это обещание: результат живёт не дольше самого короткого из аргументов.
Ошибка E0597 («borrowed value does not live long enough») значит, что ссылка пережила значение — например, вы вернули ссылку на локальную переменную. Решение — вернуть само значение (String), а не ссылку.
Когда правил не хватает
Иногда владение действительно общее. Для этого есть стандартные типы:
| Задача | Инструмент |
|---|---|
| Несколько владельцев в одном потоке | Rc<T> |
| Несколько владельцев между потоками | Arc<T> |
| Изменение через неизменяемую ссылку | RefCell<T>, Mutex<T> |
RefCell переносит проверку заимствований в рантайм: нарушение вызовет панику, а не ошибку компиляции. Используйте осознанно.
Как читать ошибки borrow checker
- Читайте сообщение целиком: компилятор показывает, где начинается заимствование, где конфликт и где ссылка используется последний раз.
- Команда
rustc --explain E0502выдаёт подробное объяснение с примерами. - Не лечите всё через
.clone(): это работает, но скрывает проблему в архитектуре данных.