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

Ошибка Rust E0597: borrowed value does not live long enough

E0597 значит: ты держишь ссылку (&x) дольше, чем живёт само значение x. Значение убирается в конце своего блока, а что-то за пределами этого блока всё ещё хочет пользоваться ссылкой. Ошибка очень частая, особенно с вложенными блоками, циклами и структурами, а исправление обычно — переставить одну строку или хранить владеющее значение вместо ссылки. (Эта страница — часть раздела Ошибки компилятора Rust с объяснениями.)

error[E0597]: `x` does not live long enough
--> src/main.rs:5:13
|
4 | let x = 5;
| - binding `x` declared here
5 | r = &x; // r заимствует x
| ^^ borrowed value does not live long enough
6 | } // здесь x удаляется
| - `x` dropped here while still borrowed
7 | println!("r: {r}"); // ❌ r указывал бы в пустоту
| - borrow later used here

Компилятор пишет: x живёт недостаточно долго. Пометки показывают, где x объявлена, где её заимствуют, где она удаляется, пока ещё заимствована, и где заимствование используется позже.

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

fn main() {
let r;
{
let x = 5;
r = &x; // r заимствует x
} // здесь x удаляется
println!("r: {r}"); // ❌ r указывал бы в пустоту
}

Четыре пометки рассказывают историю по порядку:

  1. Строка 4: x создаётся внутри вложенного блока { ... }.
  2. Строка 5: r заимствует x. Пока всё нормально.
  3. Строка 6: блок заканчивается, и x удаляется (drop). А заимствование всё ещё живо.
  4. Строка 7: r используется, но значения, на которое он указывает, уже нет.

Представь, что ты записал номер гостиничного номера друга. Это полезно, пока он там живёт. Когда он выедет, номер у тебя в записке останется, но приведёт он в пустую комнату — или к незнакомцу. Rust не даст тебе хранить записку, которая ведёт в пустую комнату.

Встречается и короткая пометка «borrowed value does not live long enough» («заимствованное значение живёт недостаточно долго»). Это та же ошибка; в заголовке просто названа переменная.

Ссылка никогда не должна жить дольше значения, на которое указывает. Иначе получилась бы висячая ссылка (dangling reference): указатель на память, которая уже освобождена и может хранить что-то другое. В C и C++ это классический источник падений и уязвимостей («use after free» — использование после освобождения).

Каждое значение в Rust удаляется, когда его владелец выходит из области видимости, — обычно на закрывающей } блока, где оно объявлено. Анализатор заимствований сравнивает два отрезка: как долго живёт значение и как долго используется ссылка. Если ссылка используется после } значения, получаешь E0597. См. Область видимости, Заимствование и Времена жизни.

Обрати внимание, на что компилятор не жалуется: на само заимствование x в строке 5. Проблема только в более позднем использовании в строке 7. Убери последнюю строку — и программа скомпилируется. Это полезная подсказка для исправлений ниже: либо пусть значение живёт дольше, либо пусть использование ссылки закончится раньше.

Исправление 1: перенеси значение во внешнюю область видимости

Заголовок раздела «Исправление 1: перенеси значение во внешнюю область видимости»

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

fn main() {
let scores = vec![72, 95, 88]; // теперь живёт до конца main
let best;
{
best = scores.iter().max().unwrap();
}
println!("Лучший результат: {best}"); // ✅ scores всё ещё жив
}

Вывод:

Лучший результат: 95
▶ Попробуй сам

Простое правило: владелец должен быть объявлен раньше заимствующего и пережить его. Если значение создаётся внутри блока или цикла, а ссылка на него используется снаружи, вынеси значение наружу.

Исправление 2: храни владеющие значения вместо ссылок

Заголовок раздела «Исправление 2: храни владеющие значения вместо ссылок»

Выбирай этот вариант, когда собираешь значения, созданные внутри цикла. Каждый проход цикла создаёт новую String и удаляет её в конце прохода, поэтому Vec<&str> снаружи цикла не может хранить ссылки на них:

fn main() {
let mut names: Vec<&str> = Vec::new();
for i in 1..=3 {
let name = format!("player{i}");
names.push(&name); // ❌ `name` живёт недостаточно долго
}
println!("{names:?}");
}

Исправление — пусть Vec владеет строками. Замени Vec<&str> на Vec<String> и клади в него саму String (это перемещает её в вектор, так что она живёт столько же, сколько вектор):

fn main() {
let mut names: Vec<String> = Vec::new();
for i in 1..=3 {
let name = format!("player{i}");
names.push(name); // перемещаем String в Vec
}
println!("{names:?}");
}

Вывод:

["player1", "player2", "player3"]
▶ Попробуй сам

Та же идея работает для структур и HashMap: если данные создаются в меньшей области видимости, чем контейнер, храни String (или Vec<T>) вместо &str (или &[T]). Для начинающих поля-владельцы почти всегда правильный выбор. См. Строки и Векторы.

Выбирай этот вариант, когда оригинал тоже нужен дальше или у тебя есть только заимствование. Структура, которая хранит ссылку (Label<'a> { text: &'a str }), не может пережить строку, на которую указывает. Дай структуре поле-владельца String и храни в нём копию:

struct Label {
text: String, // владеющая строка, а не &str
}
fn main() {
let label;
{
let title = String::from("Входящие");
label = Label { text: title.clone() };
println!("Внутри блока: {title}");
}
println!("Ярлык: {}", label.text); // ✅ у label своя копия
}

Вывод:

Внутри блока: Входящие
Ярлык: Входящие
▶ Попробуй сам

Если title потом не нужна, обойдись без клона и перемести её: Label { text: title }. Клонировать короткую строку дёшево и совершенно нормально. Если у тебя есть только &str, вызови .to_string(), чтобы получить владеющую копию.

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

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

Выбирай этот вариант, когда значение и должно жить недолго. Вместо того чтобы продлевать жизнь значения, используй ссылку, пока значение ещё живо. Это часто встречается с функциями, которые возвращают ссылку, как в классическом примере longest:

fn longest<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("Ferris the crab");
{
let second = String::from("Corro");
let winner = longest(&first, &second);
println!("Самое длинное имя: {winner}"); // ✅ используем до удаления second
}
}

Вывод:

Самое длинное имя: Ferris the crab
▶ Попробуй сам

Имена здесь английские, потому что len() считает байты, а кириллическая буква занимает 2 байта.

Если объявить winner снаружи блока и вывести после него, получишь E0597 для second, хотя first в данном случае длиннее. Сигнатура говорит, что результат может заимствовать из любого аргумента, поэтому оба должны его пережить. Если результат действительно нужен позже, храни его владеющим: let winner = longest(&first, &second).to_string();.

Замыкание хранится снаружи блока. Замыкание, которое использует name, заимствует её. Если замыкание переживает name, получишь E0597 с дополнительной пометкой value captured here («значение захвачено здесь»):

let say_hello;
{
let name = String::from("Ferris");
say_hello = || println!("Привет, {name}!"); // ❌ `name` живёт недостаточно долго
}
say_hello();

Исправление: напиши move || ..., чтобы замыкание забрало name во владение. См. Замыкания.

Потоки. Заимствование локальной переменной внутри thread::spawn(|| ...) даёт другую ошибку — E0373 («closure may outlive the current function»), потому что поток может работать дольше main. Исправление: thread::spawn(move || ...), совместное владение через Arc или std::thread::scope. См. Многопоточность.

Ссылка на временное значение. let label = Label { text: String::from("Inbox").as_str() }; похоже на E0597, но выглядит как E0716, «temporary value dropped while borrowed» («временное значение удалено, пока заимствовано»): String так и не сохраняется в переменную, поэтому удаляется в конце инструкции. Исправление: сначала положи её в переменную (let title = String::from("Inbox");) или сделай поле типа String.

Возврат ссылки из функции. Если значение создаётся внутри функции, вместо этого получишь E0515. Возвращай владеющее значение.

Проверь себя

1. В коде let r; { let x = 5; r = &x; } с println! для r после блока что именно вызывает E0597?

2. Ты создаёшь String на каждом проходе цикла и кладёшь &name в Vec<&str>. Какое исправление обычное?

3. Замыкание потока заимствует локальный Vec. Какую ошибку ты получишь?

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