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

Ошибка 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. Настоящее исправление — поменять возвращаемый тип.

Ссылка никогда не должна жить дольше значения, на которое указывает. Возврат ссылки на локальную переменную создал бы висячую ссылку — указатель на освобождённую память. В C и C++ такое компилируется, а потом падает (или, хуже того, тихо возвращает мусор). Rust ловит это при компиляции.

Возвращаемая ссылка должна заимствовать из чего-то, что живёт за пределами функции: из параметра, который передал вызывающий код, из поля self или из значения 'static, например строкового литерала. Собственные локальные переменные функции не подходят. Поэтому аннотации времён жизни не исправляют E0515: 'a только описывает, сколько живут данные, но не может продлить жизнь локальной переменной. См. Владение и Заимствование.

Выбирай этот вариант, когда функция создаёт данные. Чаще всего правильный ответ именно этот. Возвращай 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: OK
404: Not Found
418: 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.

Проверь себя

1. Функция собирает сообщение через format! и возвращает &message. Какое исправление обычно правильное?

2. Какая функция скомпилируется?

3. Когда Cow<str> — хороший возвращаемый тип?

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