Как распознать, что параметр времени жизни в сигнатуре функции связывается поздно, а не заранее?
Поздно связанный параметр времени жизни выбирается при каждом вызове функции: одна и та же функция может работать с разными конкретными временами жизни. В обычной свободной функции параметр времени жизни, используемый для описания ссылок и их связей, обычно является поздно связанным; параметр, объявленный на уровне типа или реализации, обычно связывается раньше и становится частью конкретного типа или специализации.
Практический признак позднего связывания — функцию можно рассматривать как универсальную для любой подходящей длительности заимствования, а не как функцию, уже привязанную к одному выбранному времени жизни.
Времена жизни в Rust существуют как статическая информация для проверки безопасности ссылок, а не как значения, передаваемые во время выполнения. Компилятору нужно описывать не только сам факт заимствования, но и то, какие длительности допустимы для конкретной функции.
Разделение на раннее и позднее связывание позволяет отличать параметр, являющийся частью конкретного типа или реализации, от параметра, который выбирается заново при каждом применении функции. Это особенно важно при передаче функций как значений и при проверке их совместимости с обобщёнными вызывающими сторонами.
Рассмотрим функцию, принимающую ссылку и возвращающую ссылку, связанную с входной. Если время жизни выбиралось бы один раз при объявлении функции, функция не могла бы естественно применяться к локальным заимствованиям разной длительности.
С другой стороны, время жизни типа-обёртки должно быть частью самого типа: обёртка, содержащая ссылку на данные, не может считаться одним и тем же типом независимо от условий, при которых эта ссылка допустима. Ошибка в различении этих случаев приводит к неверным выводам о том, когда функцию можно передать как значение и какие ссылки она действительно принимает.
Параметр времени жизни является поздно связанным, когда он универсально квантифицируется на уровне функции: вызывающая сторона фактически выбирает подходящую длительность для конкретного вызова. В концептуальной форме такая функция означает: «для любой допустимой длительности входной ссылки верни результат с указанной связью с ней».
Например, функция ниже не закреплена за одним конкретным временем жизни:
identity совместима с типом функции, содержащим высокоранговое ограничение for<'a>: она должна работать для любого 'a. При вызове с одной ссылкой компилятор выбирает одну длительность, при вызове с другой — другую; отдельного runtime-объекта, представляющего 'a, не существует.
Раннее связывание означает, что параметр времени жизни выбирается до применения конкретного метода или функции и участвует в формировании типа либо специализации реализации. Например, 'a у структуры Holder<'a> является параметром типа: Holder<'a> и Holder<'b> могут обозначать разные инстанцирования одного шаблона типа. Методы такой реализации работают в контексте уже выбранного параметра реализации.
Важно не путать позднее связывание с возможностью продлить ссылку. Оно лишь позволяет выбрать подходящую длительность, которая уже существует в рамках конкретного заимствования. Если результат должен пережить исходные данные или функция должна сохранить ссылку дольше разрешённого срока, позднее связывание это не исправит.
Для свободных функций параметры времени жизни в обычных ссылочных параметрах обычно являются поздно связанными. Однако точная классификация зависит от места объявления и ограничений параметров: дополнительные связи с типами и условиями where могут сделать параметр ранним. Поэтому надёжный анализ требует смотреть не только на наличие 'a, но и на область его объявления и на то, в каких типах и ограничениях он используется.
В библиотеке нужно передать обработчик текста в универсальный компонент. Обработчик не должен быть привязан к конкретной строке или к одному заранее известному времени жизни: компонент вызывает его с временными заимствованиями, создаваемыми в разных итерациях обработки.
Рассматривались два варианта. Первый — хранить обработчик, связанный с одним параметром времени жизни. Он точно описывает обработчики, захватывающие конкретные данные, но ограничивает повторное применение обработчика и может сделать тип компонента излишне зависимым от внешнего заимствования. Второй — требовать обработчик, работающий с любой длительностью входной ссылки, то есть использовать поздно связанный параметр через for<'a>.
Выбран второй вариант, потому что обработчик не хранит входную ссылку после вызова. В результате компонент принимает один универсальный обработчик и безопасно использует его с заимствованиями разной длительности, не расширяя ни одно из них и не добавляя runtime-стоимости.
1. Можно ли считать поздно связанный параметр времени жизни одним фиксированным 'static?
Нет. Позднее связывание означает универсальность по длительности, а не выбор самой длинной длительности. Функция с поздно связанным 'a должна быть корректна и для короткого локального заимствования, поэтому подмена 'a на 'static изменила бы контракт и обычно сделала бы его слишком строгим.
2. Создаёт ли позднее связывание отдельную копию функции для каждого времени жизни?
Нет. Времена жизни участвуют в статической проверке типов и заимствований и не являются обычными runtime-параметрами. Компилятор проверяет применимость функции к конкретным ссылкам, но позднее связывание само по себе не требует отдельной исполняемой сущности для каждого 'a.
3. Достаточно ли объявить одинаковое имя 'a в двух местах, чтобы получить позднюю связь между ними?
Нет. Имена параметров времени жизни локальны своей области объявления. 'a в одной функции не связан с 'a в другой; связь появляется только внутри одной сигнатуры, типа или набора ограничений, где этот параметр объявлен и используется. Поэтому одинаковое написание имени ничего не говорит о тождестве длительностей между разными объявлениями.