Ошибка 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 так делает»Rust гарантирует, что ссылка никогда не переживёт данные, на которые указывает (никаких «висячих указателей»). Чтобы проверить это для вызова функции, компилятор смотрит только на сигнатуру функции, а не на её тело. Поэтому сама сигнатура должна говорить, откуда берётся возвращаемая ссылка.
Чаще всего времена жизни писать не нужно, потому что Rust подставляет их сам по трём простым правилам неявного выведения (elision rules):
- Каждый параметр-ссылка получает своё время жизни.
- Если параметр-ссылка ровно один, возвращаемая ссылка заимствует из него.
- Если это метод с
&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 везде — это работает, но строже, чем нужно.
Исправление 2: верни владеющую String
Заголовок раздела «Исправление 2: верни владеющую String»Выбирай этот вариант, когда функция создаёт новый текст или когда просто не хочется привязывать вызывающий код к заимствованию. Владеющая 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 означает OK404 означает 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.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0382: borrow of moved value: ещё одна ошибка владения — использование значения после того, как ты его отдал.
- E0502: изменяемое заимствование при неизменяемом: анализатор заимствований в деле, когда времена жизни уже на месте.
- E0499: два изменяемых заимствования: две
&mutна одни и те же данные одновременно. - E0308: mismatched types: например, возврат
Stringиз функции, которая обещает&str. E0515: cannot return reference to local variable: ты применил исправление 1 к данным, созданным внутри функции. Вместо этого верни владеющуюString(исправление 2).- Все ошибки компилятора с объяснениями
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Почему fn first_word(s: &str) -> &str компилируется без всякого времени жизни?
2. Твоя структура Config { path: &str } даёт E0106. Какое исправление обычно лучше всего?
3. Функция собирает сообщение через format! и возвращает его. Какой у неё должен быть возвращаемый тип?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0106 — Rust error codes index (на английском): официальное объяснение этой ошибки.
- Валидация ссылок посредством сроков жизни — книга «Язык программирования Rust» (перевод на русский): полная история времён жизни, включая пример
longestи структуры со ссылками. - Lifetime elision — The Rust Reference (на английском): точные правила, по которым Rust подставляет времена жизни за тебя.
- Сокрытие — Rust на примерах (перевод на русский): короткие примеры того, когда времена жизни можно не писать.
- Времена жизни — Rust на примерах (перевод на русский): примеры явных времён жизни в функциях, методах и структурах, которые можно запустить.