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

Ошибка Rust E0106: missing lifetime specifier (не указано время жизни)

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

error[E0106]: missing lifetime specifier
--> src/main.rs:1:32
|
1 | fn longer(a: &str, b: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `a` or `b`
help: consider introducing a named lifetime parameter
|
1 | fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
| ++++ ++ ++ ++

«Missing lifetime specifier» значит «не указано время жизни». Подсказка объясняет: возвращаемый тип содержит заимствованное значение, но сигнатура не говорит, заимствовано оно из a или из b. Компилятор предлагает ввести именованный параметр времени жизни.

Программа, которая вызывает ошибку:

fn longer(a: &str, b: &str) -> &str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
println!("{}", longer("apple", "fig"));
}

Ссылка — это заимствование: она указывает на данные, которыми владеет кто-то другой. Когда функция возвращает ссылку, Rust должен знать, чьи данные она заимствует, чтобы проверить, что они остаются живыми, пока вызывающий код пользуется результатом.

Посмотри на три пометки под строкой 1. Компилятор подчёркивает a и b значками ----, а возвращаемый тип — значком ^. Он говорит: «ты возвращаешь заимствованный &str, и он может прийти из a или из b. Откуда именно?» Угадывать он не станет.

Представь библиотечный формуляр. Если друг даёт тебе книгу и говорит «она взята почитать», ты хочешь знать, у кого, чтобы понимать, когда её вернуть. Время жизни (lifetime) — это такая пометка на заимствовании.

Rust гарантирует, что ссылка никогда не переживёт данные, на которые указывает (никаких «висячих указателей»). Чтобы проверить это для вызова функции, компилятор смотрит только на сигнатуру функции, а не на её тело. Поэтому сама сигнатура должна говорить, откуда берётся возвращаемая ссылка.

Чаще всего времена жизни писать не нужно, потому что Rust подставляет их сам по трём простым правилам неявного выведения (elision rules):

  1. Каждый параметр-ссылка получает своё время жизни.
  2. Если параметр-ссылка ровно один, возвращаемая ссылка заимствует из него.
  3. Если это метод с &self или &mut self, возвращаемая ссылка заимствует из self.

E0106 появляется, когда ни одно из этих правил не даёт ответа: два и больше параметров-ссылок (и нет self), ни одного параметра-ссылки или ссылка внутри структуры. Подробно об этом — в уроке Времена жизни, а о самих ссылках — в уроке Заимствование.

Исправление 1: добавь именованное время жизни ('a)

Заголовок раздела «Исправление 1: добавь именованное время жизни ('a)»

Выбирай этот вариант, когда функция действительно возвращает кусочек одного из своих входных значений. 'a — просто имя. Если написать его у входов и у выхода, это значит «результат живёт столько же, сколько эти входные значения»:

// 'a: возвращаемый &str заимствует из a или b, поэтому живёт столько же, сколько оба.
fn longer<'a>(a: &'a str, b: &'a str) -> &'a str {
if a.len() >= b.len() { a } else { b }
}
fn main() {
let first = String::from("apple");
let second = String::from("fig");
println!("Более длинное слово: {}", longer(&first, &second));
}

Вывод:

Более длинное слово: apple
▶ Попробуй сам

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

Если результат всегда приходит только из одного параметра, ставь 'a только на него: fn lookup<'a>(map: &'a HashMap<String, String>, key: &str) -> Option<&'a str>. Компилятор в подсказке ставит 'a везде — это работает, но строже, чем нужно.

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

fn greeting(name: &str, lang: &str) -> String {
if lang == "ru" {
format!("Привет, {name}!")
} else {
format!("Hello, {name}!")
}
}
fn main() {
println!("{}", greeting("Ana", "ru"));
println!("{}", greeting("Sam", "en"));
}

Вывод:

Привет, Ana!
Hello, Sam!
▶ Попробуй сам

Это стоит одного маленького выделения памяти. Для большинства программ это совершенно нормально. Не переживай из-за этого.

Исправление 3: храни в структуре свои данные

Заголовок раздела «Исправление 3: храни в структуре свои данные»

Поле структуры вроде title: &str даёт ту же ошибку:

error[E0106]: missing lifetime specifier
--> src/main.rs:2:12
|
2 | title: &str,
| ^ expected named lifetime parameter

Для структур лучшее исправление почти всегда — владеть данными: используй String вместо &str и Vec<T> вместо &[T]. Тогда структура может жить сколько угодно:

#[derive(Debug)]
struct Book {
title: String, // владеющая строка, а не &str
pages: u32,
}
fn main() {
let book = Book { title: String::from("Дюна"), pages: 412 };
println!("{} ({} стр.)", book.title, book.pages);
}

Вывод:

Дюна (412 стр.)
▶ Попробуй сам

Давай структуре время жизни, только когда она — недолговечный взгляд на данные, которые живут где-то ещё, например парсер, который смотрит на большой текст. Тогда пиши <'a> у структуры и у поля:

struct Excerpt<'a> {
text: &'a str, // заимствует из романа ниже
}
fn main() {
let novel = String::from("Зовите меня Измаил. Несколько лет назад...");
let first = novel.split('.').next().unwrap();
let excerpt = Excerpt { text: first };
println!("Отрывок: {}", excerpt.text);
}

Вывод:

Отрывок: Зовите меня Измаил
▶ Попробуй сам

Исправление 4: используй 'static для строковых литералов

Заголовок раздела «Исправление 4: используй 'static для строковых литералов»

Когда у функции вообще нет параметров-ссылок, ошибка говорит «there is no value for it to be borrowed from» («нет значения, из которого его можно было бы заимствовать»):

error[E0106]: missing lifetime specifier
--> src/main.rs:1:22
|
1 | fn default_name() -> &str {
| ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from

Если ты возвращаешь только строковые литералы вроде "OK", пиши &'static str. Литералы вшиты в программу, поэтому живут всё время её работы:

fn status_text(code: u16) -> &'static str {
match code {
200 => "OK",
404 => "Not Found",
_ => "Unknown",
}
}
fn main() {
println!("200 означает {}", status_text(200));
println!("404 означает {}", status_text(404));
}

Вывод:

200 означает OK
404 означает Not Found
▶ Попробуй сам

Используй 'static только для настоящих литералов и констант. Если текст создаётся во время выполнения (через format! или чтением из файла), 'static не поможет: возвращай String (исправление 2).

  • Вспомогательные функции с двумя входами, например fn lookup(map: &HashMap<String, String>, key: &str) -> Option<&str>. Исправление: свяжи выход только с map через 'a (исправление 1).
  • Структуры, которые хранят текст, например User { name: &str }, собранный из разобранного ввода. Исправление: поля типа String (исправление 3).
  • Функции, которые возвращают фиксированные сообщения, например fn error_label() -> &str. Исправление: &'static str (исправление 4).

С методами хорошие новости: fn name(&self) -> &str вообще не нуждается во времени жизни благодаря правилу 3.

Проверь себя

1. Почему fn first_word(s: &str) -> &str компилируется без всякого времени жизни?

2. Твоя структура Config { path: &str } даёт E0106. Какое исправление обычно лучше всего?

3. Функция собирает сообщение через format! и возвращает его. Какой у неё должен быть возвращаемый тип?

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