Лайфтаймы в Rust
Тема, на которой валится больше всего кандидатов - обычно потому, что лайфтайм понимают как способ управлять памятью. Спрашивают, что означает 'a в сигнатуре, когда его можно опустить, что такое 'static. Проверяют, понимаешь ли ты, что это язык описания связей, а не рычаг влияния на рантайм.
Стержень: аннотация не продлевает и не сокращает жизнь значения - она сообщает компилятору, с чем связан результат, чтобы тот проверил корректность.
// Формулировки: «что делает 'a?», «почему longest не компилируется без аннотаций?», «что значит T: 'static?»
Аннотация описывает связь
fn f<'a>(x: &'a str) -> &'a str читается так: результат живёт не дольше, чем аргумент x. Компилятор проверяет это на стороне вызова: если вернуть ссылку и уронить источник, сборка упадёт.
На машинный код аннотации не влияют вообще - они стираются после проверки. Поэтому фраза «лайфтайм продлевает жизнь значения» неверна: жизнью управляет владение, а лайфтайм лишь описывает уже существующие отношения.
// Из-за этого лайфтайм часто называют не свойством ссылки, а ограничением на область, где ей можно пользоваться.
- лайфтайм
- описание связи времени жизни результата с аргументами
- стирание
- лайфтаймы исчезают после проверки и не попадают в код
Elision: когда писать не нужно
Правила вывода простые. Каждой входной ссылке даётся свой лайфтайм. Если вход один, его лайфтайм уходит в результат. Если среди аргументов есть &self, результат берёт лайфтайм self, поэтому у методов аннотации почти никогда не нужны.
А вот fn longest(x: &str, y: &str) -> &str не соберётся: кандидатов два, и компилятор не угадывает, с каким связан результат. Ошибка будет E0106, missing lifetime specifier. Пишем <'a> и связываем оба входа с выходом - теперь вызывающий обязан следить, чтобы обе строки жили достаточно долго.
// Тот же E0106 вылезет при попытке вернуть ссылку на локальную переменную. Диагноз там другой, но начинается всё с «не с чем связать результат».
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}- elision
- правила автоматического вывода лайфтаймов
- E0106
- компилятор не знает, с чем связать возвращаемую ссылку
Структуры со ссылками и два смысла 'static
Если структура хранит ссылку, она обязана объявить параметр: struct Parser<'a> { input: &'a str }. Экземпляр не должен пережить данные, на которые смотрит, и параметр заражает все места использования. Когда это мешает, поле делают владеющим (String) или берут Cow, чтобы копировать лишь при необходимости.
У 'static два разных смысла, и их путают постоянно. &'static str - ссылка, валидная всё время работы программы: например, строковый литерал из бинарника. T: 'static - ограничение на тип: он не содержит заимствований короче программы. Ему удовлетворяет и String, созданная в рантайме, потому что она владеет данными, а не заимствует их.
// Второй смысл встречается в thread::spawn: замыкание обязано быть 'static - не потому, что должно жить вечно, а потому, что не должно ссылаться на чужие локальные переменные.
- &'static
- ссылка, живущая всю программу
- T: 'static
- тип не содержит более коротких заимствований
Как отвечать: «Что означает 'static и почему его требует thread::spawn?»
У 'static два смысла. В типе ссылки - данные живут всё время работы программы, как строковый литерал. В ограничении T: 'static - тип не содержит заимствований короче программы; под это подходит и String, созданная в рантайме, потому что она владеет своими данными. thread::spawn требует именно второго: поток может пережить функцию, которая его запустила, поэтому замыкание не должно ссылаться на её локальные переменные. Обычно это решается move, а если владение отдавать не хочется - thread::scope, там область гарантирует завершение потоков и требование снимается.
Ты разводишь два смысла - то самое место, где путается большинство, и сразу связываешь их с практикой потоков.
На чём валятся
- −Говорят, что лайфтайм продлевает жизнь значения. Он её только описывает.
- −Считают 'static синонимом «лежит в бинарнике» - String с owned-данными тоже подходит под T: 'static.
- −Ставят аннотации наугад, пока не соберётся, вместо того чтобы понять связь входа и выхода.
- −Пытаются вернуть ссылку на локальную переменную и не понимают диагностику.
- −Не знают правил elision и пишут лайфтаймы там, где компилятор выводит их сам.
Проверьте себя
Пять вопросов из банка по этой подтеме. Всего их 12, остальные разбираются в тренажёре.
- Почему fn first(s: &str) -> &str компилируется без явных лайфтаймов, а fn longest(x: &str, y: &str) -> &str — нет?A)Elision работает только для типов из стандартной библиотекиB)Во втором случае нужен 'static, а его выводить не разрешеноC)Elision выводит лайфтайм при одном входном; при двух он неоднозначенD)Второй возвращает ссылку на временное значение — оно не переживёт вызов
показать ответ и разбор
+C)Elision выводит лайфтайм при одном входном; при двух он неоднозначен// разбор: Правила elision простые: каждому входному параметру-ссылке даётся свой лайфтайм; если вход один, его лайфтайм уходит в результат; если есть &self, результат берёт его лайфтайм. У longest два кандидата, и компилятор не угадывает — просит написать <'a> и связать входы с выходом явно.
- Структура хранит поле r: &str. Что требует компилятор?A)Параметр лайфтайма: struct S<'a> { r: &'a str }B)Пометить структуру #[derive(Lifetime)]C)Добавить 'static: ссылки в полях обязаны жить всю программуD)Заменить поле на String: ссылка в поле недопустима
показать ответ и разбор
+A)Параметр лайфтайма: struct S<'a> { r: &'a str }// разбор: Структура со ссылкой обязана объявить, с чьим временем жизни она связана: экземпляр не должен пережить данные, на которые смотрит. Отсюда S<'a> и заражение параметром всех использований. Когда такая связанность мешает, поле делают владеющим (String) или берут Cow, если копия нужна не всегда.
- Что означает 'static в &'static str и в ограничении T: 'static?A)И там, и там — данные лежат в статической памяти бинарникаB)Ссылка живёт всю программу; тип не содержит коротких ссылокC)И там, и там — значение размещено до входа в mainD)Ссылка на константу; тип реализован без внутренних указателей
показать ответ и разбор
+B)Ссылка живёт всю программу; тип не содержит коротких ссылок// разбор: Смыслов два. &'static str — ссылка, валидная всё время работы программы (например, строковый литерал). T: 'static — ограничение на тип: он не содержит ссылок с более коротким лайфтаймом. Ему удовлетворяет и String, созданная в рантайме, потому что она владеет данными, а не заимствует их.
- Функция возвращает ссылку на локальную String. Что скажет компилятор и почему?A)Соберёт, но вернёт висячую ссылку — ошибка вылезет в рантаймеB)Предложит пометить возврат как 'static и на этом успокоитсяC)Соберёт: компилятор продлит жизнь локальной переменной до конца вызоваD)Потребует лайфтайм, а с ним укажет, что значение не живёт достаточно долго
показать ответ и разбор
+D)Потребует лайфтайм, а с ним укажет, что значение не живёт достаточно долго// разбор: Сначала спотыкается elision: входных ссылок нет, и связать результат не с чем — E0106, missing lifetime specifier. Если приписать 'a или 'static, диагноз станет точнее: локальная переменная умирает на выходе из функции, и ссылка на неё не проживёт. Правильный ответ — возвращать владеющий String.
- Влияют ли аннотации лайфтаймов на размер значения и на скорость программы?A)Да: каждая ссылка носит с собой отметку о времени жизни объектаB)Нет: это информация для проверки, в машинный код она не попадаетC)Да: 'static перемещает значение в статическую памятьD)Нет, но замедляют сборку в разы
показать ответ и разбор
+B)Нет: это информация для проверки, в машинный код она не попадает// разбор: Лайфтаймы стираются после проверки: ссылка остаётся обычным адресом, никаких счётчиков и меток в рантайме нет. Это ключевое отличие от подсчёта ссылок и сборки мусора — безопасность оплачена работой компилятора, а не работой программы. Поэтому и производительность не зависит от того, много ли аннотаций в коде.
дальше
Теорию прочитали. Навык ставится повторением
В Сеньорчике эта подтема идёт в ежедневных сессиях: движок возвращает её, пока ответы не станут уверенными, и ведёт прогресс отдельно по каждой подтеме. Теория внутри тоже бесплатна, лимит только на количество вопросов в день.