Ошибка Rust E0515: cannot return reference to local variable
E0515 значит: твоя функция пытается вернуть ссылку (&) на то, что она сама создала. Всё, что создаёт функция, убирается, когда она возвращает управление, так что ссылка указывала бы в пустоту. На эту ошибку натыкается почти каждый новичок в Rust, а исправление обычно — убрать один & и вернуть само значение. (Эта страница — часть раздела Ошибки компилятора Rust с объяснениями.)
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0515]: cannot return reference to local variable `greeting` --> src/main.rs:3:5 |3 | &greeting // ❌ greeting удаляется при возврате | ^^^^^^^^^ returns a reference to data owned by the current functionКомпилятор пишет: нельзя вернуть ссылку на локальную переменную greeting — это ссылка на данные, которыми владеет текущая функция.
Вот программа, которая выдаёт эту ошибку:
fn make_greeting(name: &str) -> &str { let greeting = format!("Привет, {name}!"); // новая String, ею владеет эта функция &greeting // ❌ greeting удаляется при возврате}
fn main() { let text = make_greeting("Ferris"); println!("{text}");}У этой ошибки несколько формулировок. Все они значат одно и то же:
cannot return reference to local variable— когда ты пишешь&greeting.cannot return value referencing local variable— когда вызываешь заимствующий метод, напримерgreeting.as_str()или&scores[2..].cannot return value referencing temporary value— когда данные вообще не сохранялись в переменную, напримерtext.to_uppercase().as_str().
У каждой версии есть пометка «returns a value referencing data owned by the current function» («возвращает значение, ссылающееся на данные, которыми владеет текущая функция»).
Что это значит
Заголовок раздела «Что это значит»greeting — локальная переменная: она создаётся внутри make_greeting и принадлежит ей. Когда функция возвращает управление, все её локальные переменные удаляются. Возвращённый &str указывал бы внутрь String, которой больше нет.
Это как съехать с квартиры и отдать другу ключ. Ключ настоящий, но квартира теперь пустая или там живёт кто-то другой. Другу нужна мебель, а не ключ.
Частый путь к этой ошибке: ты сначала вернул greeting (String) из функции, которая возвращает &str, получил E0308 (несовпадение типов), а компилятор предложил «consider borrowing here: &greeting». Это предложение исправляет тип, но ведёт прямиком к E0515. Настоящее исправление — поменять возвращаемый тип.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Ссылка никогда не должна жить дольше значения, на которое указывает. Возврат ссылки на локальную переменную создал бы висячую ссылку — указатель на освобождённую память. В C и C++ такое компилируется, а потом падает (или, хуже того, тихо возвращает мусор). Rust ловит это при компиляции.
Возвращаемая ссылка должна заимствовать из чего-то, что живёт за пределами функции: из параметра, который передал вызывающий код, из поля self или из значения 'static, например строкового литерала. Собственные локальные переменные функции не подходят. Поэтому аннотации времён жизни не исправляют E0515: 'a только описывает, сколько живут данные, но не может продлить жизнь локальной переменной. См. Владение и Заимствование.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: верни владеющее значение
Заголовок раздела «Исправление 1: верни владеющее значение»Выбирай этот вариант, когда функция создаёт данные. Чаще всего правильный ответ именно этот. Возвращай String вместо &str (или Vec<T> вместо &[T]) и убери &. Значение перемещается к вызывающему коду, и теперь он им владеет:
fn make_greeting(name: &str) -> String { let greeting = format!("Привет, {name}!"); greeting // ✅ отдаём вызывающему коду всю String}
fn top_scores() -> Vec<u32> { let mut scores = vec![42, 97, 63, 88]; scores.sort(); scores.split_off(2) // два наибольших, в новом Vec}
fn main() { let text = make_greeting("Ferris"); println!("{text}"); println!("{:?}", top_scores());}Вывод:
Привет, Ferris![88, 97]Не переживай о цене: при возврате String или Vec перемещаются только маленький указатель, длина и ёмкость. Сам текст не копируется. Можно было бы и просто написать последней строкой format!("Привет, {name}!").
Исправление 2: прими данные параметром и верни ссылку внутрь них
Заголовок раздела «Исправление 2: прими данные параметром и верни ссылку внутрь них»Выбирай этот вариант, когда вызывающий код уже владеет данными, а ты хочешь просто указать на их часть. Если функция работает с заимствованным параметром, возвращаемая ссылка заимствует из этого параметра, а он живёт за пределами функции:
fn top_two(scores: &mut [u32]) -> &[u32] { scores.sort(); &scores[scores.len() - 2..] // ✅ указывает внутрь данных вызывающего кода}
fn main() { let mut scores = vec![42, 97, 63, 88]; // им владеет main let best = top_two(&mut scores); println!("{best:?}");}Вывод:
[88, 97]С одним параметром-ссылкой Rust сам подставляет время жизни. С двумя и больше может понадобиться 'a (см. E0106). Это работает, только если ты возвращаешь кусочек входных данных. Совершенно новое значение, построенное из них (to_uppercase(), format!, trim().to_string()), — снова локальная переменная, так что возвращайся к исправлению 1. См. Срезы.
Исправление 3: возвращай &'static str для строковых литералов
Заголовок раздела «Исправление 3: возвращай &'static str для строковых литералов»Выбирай этот вариант, когда каждый возможный результат — фиксированный текст, написанный в твоём коде. Строковые литералы хранятся в самой программе и живут вечно, так что возвращать ссылку на литерал всегда безопасно:
fn status_text(code: u16) -> &'static str { match code { 200 => "OK", 404 => "Not Found", 500 => "Internal Server Error", _ => "Unknown", }}
fn main() { for code in [200, 404, 418] { println!("{code}: {}", status_text(code)); }}Вывод:
200: OK404: Not Found418: UnknownНе пытайся «исправить» результат format!, написав в сигнатуре &'static str. Ты просто снова получишь E0515, потому что String всё равно локальная.
Исправление 4: используй Cow, когда иногда заимствуешь, а иногда создаёшь
Заголовок раздела «Исправление 4: используй Cow, когда иногда заимствуешь, а иногда создаёшь»Выбирай этот вариант, когда функция обычно возвращает входные данные без изменений, но иногда должна построить новую строку. Cow (clone-on-write, «копирование при записи») хранит либо заимствованный &str, либо владеющую String, так что память выделяется, только когда это действительно нужно:
use std::borrow::Cow;
fn safe_file_name(name: &str) -> Cow<'_, str> { if name.contains(' ') { Cow::Owned(name.replace(' ', "_")) // новая String, только когда нужно } else { Cow::Borrowed(name) // вообще без копирования }}
fn main() { for name in ["отчёт.txt", "мои заметки.txt"] { let clean = safe_file_name(name); let kind = match clean { Cow::Borrowed(_) => "заимствована", Cow::Owned(_) => "своя", }; println!("{clean} ({kind})"); }}Вывод:
отчёт.txt (заимствована)мои_заметки.txt (своя)Cow — это оптимизация. Если не уверен, что она нужна, возвращай String (исправление 1). Подробнее — в уроке Weak и Cow.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»Возврат s.as_str() от локальной String. greeting.as_str() заимствует greeting, так что падает так же, как &greeting, с формулировкой «cannot return value referencing local variable». Исправление: верни greeting, а возвращаемым типом сделай String.
.map(), который создаёт новые строки. Выглядит безобидно, но каждый to_lowercase() создаёт временную String, которая удаляется сразу после .as_str():
fn tags(input: &str) -> Vec<&str> { input.split(',').map(|t| t.trim().to_lowercase().as_str()).collect() // ❌ cannot return value referencing temporary value}Исправление: возвращай Vec<String> и убери .as_str(): .map(|t| t.trim().to_lowercase()).collect(). (Без to_lowercase() выражение t.trim() — это срез input, и Vec<&str> бы сработал. См. Итераторы.)
Метод, который форматирует поле. fn full_name(&self) -> &str { &format!("{} {}", self.first, self.last) } даёт «cannot return reference to temporary value». Исправление: -> String. А вот вернуть &self.first можно, потому что эти данные живут в структуре, а не в методе. См. Структуры.
Функция без параметров-ссылок. fn make_greeting() -> &String падает раньше, с E0106, потому что ей не из чего заимствовать. Исправление то же: возвращай String.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0106: missing lifetime specifier: часто появляется первой; добавление
'aне исправит ссылку на локальную переменную. - E0597: borrowed value does not live long enough: та же проблема внутри одной функции — с блоком или циклом вместо возврата.
- E0308: mismatched types: возврат
Stringтам, где объявлен&str. Меняй возвращаемый тип, а не добавляй&. - E0382: borrow of moved value: перейдя на владеющие значения, следи, чтобы не использовать значение после перемещения.
E0716: temporary value dropped while borrowed: ссылка на временное значение, которая используется позже в той же функции.- Все ошибки компилятора с объяснениями
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Функция собирает сообщение через format! и возвращает &message. Какое исправление обычно правильное?
2. Какая функция скомпилируется?
3. Когда Cow<str> — хороший возвращаемый тип?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0515 — Rust error codes index (на английском): официальное объяснение этой ошибки.
- Висячие ссылки — книга «Язык программирования Rust» (перевод на русский): пример
dangleи почему правильный ответ — вернутьStringнапрямую. - Валидация ссылок посредством сроков жизни — книга «Язык программирования Rust» (перевод на русский): почему возвращаемая ссылка должна быть привязана к одному из параметров.
- Тип среза — книга «Язык программирования Rust» (перевод на русский): возврат срезов
&str, которые заимствуют из входных данных, — безопасная версия исправления 2. Cow— документация стандартной библиотеки (на английском): тип «копирование при записи» из исправления 4, с примерами.