Перейти к содержанию
Innovialabs

Владение и заимствование в Rust: подробное объяснение

Продвинутое3 мин

Коротко

У каждого значения в Rust один владелец; его можно переместить или одолжить — много раз на чтение или один раз на изменение — и ссылка не может пережить само значение.

Три правила, на которых всё держится

Rust управляет памятью без сборщика мусора благодаря владению. Правила такие:

  1. У каждого значения есть ровно один владелец — переменная.
  2. Когда владелец выходит из области видимости, значение освобождается.
  3. Значение можно одолжить по ссылке: либо сколько угодно неизменяемых ссылок &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(): это работает, но скрывает проблему в архитектуре данных.

Частые вопросы

Потому что копирование данных в куче стоит ресурсов, а Rust делает дорогие операции явными. Автоматически копируются только типы с Copy.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.