сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Владение и лайфтаймы

Лайфтаймы в 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, остальные разбираются в тренажёре.

  1. #rs_lifetimes1 / 5
    Почему 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> и связать входы с выходом явно.

  2. #rs_lifetimes2 / 5
    Структура хранит поле 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, если копия нужна не всегда.

  3. #rs_lifetimes3 / 5
    Что означает 'static в &'static str и в ограничении T: 'static?
    A)И там, и там — данные лежат в статической памяти бинарника
    B)Ссылка живёт всю программу; тип не содержит коротких ссылок
    C)И там, и там — значение размещено до входа в main
    D)Ссылка на константу; тип реализован без внутренних указателей
    показать ответ и разбор
    +B)Ссылка живёт всю программу; тип не содержит коротких ссылок

    // разбор: Смыслов два. &'static str — ссылка, валидная всё время работы программы (например, строковый литерал). T: 'static — ограничение на тип: он не содержит ссылок с более коротким лайфтаймом. Ему удовлетворяет и String, созданная в рантайме, потому что она владеет данными, а не заимствует их.

  4. #rs_lifetimes4 / 5
    Функция возвращает ссылку на локальную String. Что скажет компилятор и почему?
    A)Соберёт, но вернёт висячую ссылку — ошибка вылезет в рантайме
    B)Предложит пометить возврат как 'static и на этом успокоится
    C)Соберёт: компилятор продлит жизнь локальной переменной до конца вызова
    D)Потребует лайфтайм, а с ним укажет, что значение не живёт достаточно долго
    показать ответ и разбор
    +D)Потребует лайфтайм, а с ним укажет, что значение не живёт достаточно долго

    // разбор: Сначала спотыкается elision: входных ссылок нет, и связать результат не с чем — E0106, missing lifetime specifier. Если приписать 'a или 'static, диагноз станет точнее: локальная переменная умирает на выходе из функции, и ссылка на неё не проживёт. Правильный ответ — возвращать владеющий String.

  5. #rs_lifetimes5 / 5
    Влияют ли аннотации лайфтаймов на размер значения и на скорость программы?
    A)Да: каждая ссылка носит с собой отметку о времени жизни объекта
    B)Нет: это информация для проверки, в машинный код она не попадает
    C)Да: 'static перемещает значение в статическую память
    D)Нет, но замедляют сборку в разы
    показать ответ и разбор
    +B)Нет: это информация для проверки, в машинный код она не попадает

    // разбор: Лайфтаймы стираются после проверки: ссылка остаётся обычным адресом, никаких счётчиков и меток в рантайме нет. Это ключевое отличие от подсчёта ссылок и сборки мусора — безопасность оплачена работой компилятора, а не работой программы. Поэтому и производительность не зависит от того, много ли аннотаций в коде.

дальше

Теорию прочитали. Навык ставится повторением

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