Ошибка 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 here5 | r = &x; // r заимствует x | ^^ borrowed value does not live long enough6 | } // здесь x удаляется | - `x` dropped here while still borrowed7 | 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 указывал бы в пустоту}Что это значит
Заголовок раздела «Что это значит»Четыре пометки рассказывают историю по порядку:
- Строка 4:
xсоздаётся внутри вложенного блока{ ... }. - Строка 5:
rзаимствуетx. Пока всё нормально. - Строка 6: блок заканчивается, и
xудаляется (drop). А заимствование всё ещё живо. - Строка 7:
rиспользуется, но значения, на которое он указывает, уже нет.
Представь, что ты записал номер гостиничного номера друга. Это полезно, пока он там живёт. Когда он выедет, номер у тебя в записке останется, но приведёт он в пустую комнату — или к незнакомцу. Rust не даст тебе хранить записку, которая ведёт в пустую комнату.
Встречается и короткая пометка «borrowed value does not live long enough» («заимствованное значение живёт недостаточно долго»). Это та же ошибка; в заголовке просто названа переменная.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Ссылка никогда не должна жить дольше значения, на которое указывает. Иначе получилась бы висячая ссылка (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]). Для начинающих поля-владельцы почти всегда правильный выбор. См. Строки и Векторы.
Исправление 3: клонируй
Заголовок раздела «Исправление 3: клонируй»Выбирай этот вариант, когда оригинал тоже нужен дальше или у тебя есть только заимствование. Структура, которая хранит ссылку (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. Возвращай владеющее значение.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0515: cannot return reference to local variable: та же проблема, но через границу функции.
- E0373: closure may outlive the current function: версия этой ошибки для потоков. Обычно исправляется через
move. - E0106: missing lifetime specifier: Rust должен знать, из чего заимствует возвращаемая или хранимая ссылка.
- E0382: borrow of moved value: противоположная ловушка — использование значения после того, как ты его отдал.
- E0502: изменяемое заимствование при неизменяемом: ссылка всё ещё используется, когда ты пытаешься изменить значение.
E0716: temporary value dropped while borrowed: как E0597, но для значения, которое никогда не хранилось в переменной.- Все ошибки компилятора с объяснениями
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. В коде let r; { let x = 5; r = &x; } с println! для r после блока что именно вызывает E0597?
2. Ты создаёшь String на каждом проходе цикла и кладёшь &name в Vec<&str>. Какое исправление обычное?
3. Замыкание потока заимствует локальный Vec. Какую ошибку ты получишь?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0597 — Rust error codes index (на английском): официальное объяснение этой ошибки.
- Анализатор заимствований — книга «Язык программирования Rust» (перевод на русский): тот же пример с
rиxи схема двух времён жизни. - Заимствование — Rust на примерах (перевод на русский): короткие примеры заимствований и того, сколько они длятся, которые можно запустить.
- Времена жизни — Rust на примерах (перевод на русский): как компилятор видит жизнь каждого значения и каждого заимствования.
- E0716 — Rust error codes index (на английском): близкая ошибка «temporary value dropped while borrowed».