Почему Rust не выводит время жизни поля-ссылки в объявлении структуры по правилам, применяемым к параметрам функции?
В объявлении структуры нет контекста вызова, по которому можно было бы однозначно определить, с каким внешним временем жизни связано поле-ссылка. Поэтому параметр времени жизни должен быть явно частью определения структуры: он описывает условие, при котором экземпляр структуры остаётся корректным.
В сигнатуре функции Rust может применять правила elision, потому что там есть формальные правила связи входных и возвращаемых ссылок. Для поля структуры такого однозначного правила нет.
Время жизни в Rust нужно не для управления памятью вручную, а для статической проверки того, что ссылка не переживает объект, на который она указывает. Структуры со ссылками сделали бы эту проверку невозможной без явного описания зависимости между временем жизни экземпляра и временем жизни заимствованных данных.
Подход с явным параметром времени жизни позволяет сохранить структуру без владения данными, но при этом не скрывать от компилятора её ограничение: экземпляр нельзя использовать дольше, чем допустимо использовать содержащиеся в нём ссылки.
Рассмотрим тип, содержащий ссылку. Компилятор должен знать, какое условие действует для каждого экземпляра: ссылка может указывать на данные из локального блока, из внешнего объекта или на данные со статическим временем жизни.
Если бы время жизни выводилось автоматически в объявлении типа, один и тот же тип мог бы получать разные, неочевидные правила в зависимости от места использования. Это нарушило бы ясность контракта типа и не дало бы определить, как время жизни поля связано с временем жизни самого экземпляра.
Параметр времени жизни у структуры является частью её типа и связывает допустимое существование экземпляра с действительностью ссылки:
Здесь View<'a> означает: экземпляр содержит ссылку, действительную как минимум в течение 'a. Это не продлевает жизнь строки и не назначает конкретную длительность заимствования; параметр лишь переносит уже существующее ограничение в тип структуры.
В сигнатуре функции elision может использовать правило: при одном входном времени жизни возвращаемая ссылка обычно связывается с ним, а у метода ссылка на self имеет специальный приоритет. У объявления структуры нет аналогичного набора входов и результата, поэтому компилятор не может выбрать корректную связь автоматически.
Аннотация также не означает, что все ссылки в структуре обязаны иметь одинаковую фактическую длительность. Если полей несколько, у них могут быть разные параметры времени жизни, например Pair<'a, 'b>. Один параметр используется только тогда, когда контракт действительно требует выразить общую связь.
Компромисс таков: определение типа становится более подробным, зато зависимости видны в API, а ошибки использования обнаруживаются на этапе компиляции. Если структуре не нужно заимствовать данные, альтернативой может быть владение значением, например String вместо &str, но это меняет стоимость копирования, владение и архитектуру API.
Представим парсер, который возвращает представление фрагмента входного текста без копирования. Вариант с полем String делает результат независимым от исходной строки, но требует выделения памяти и копирования данных. Вариант со ссылкой эффективнее, однако результат нельзя сохранить после уничтожения исходного буфера.
Выбранное решение — структура с параметром времени жизни View<'a>. Она позволяет явно показать пользователю API, что результат заимствует входные данные, а компилятор не позволит вернуть или сохранить такой результат там, где исходный текст уже недействителен. Это обычно предпочтительнее неявного владения, когда производительность и отсутствие копирования важны.
Дополнительный вопрос 1: Продлевает ли View<'a> время жизни данных, на которые ссылается?
Нет. Параметр 'a только устанавливает требование: ссылка внутри View<'a> должна оставаться действительной в соответствующем диапазоне использования. Владелец данных по-прежнему определяет, когда они будут уничтожены, а структура не может сделать этот срок длиннее.
Дополнительный вопрос 2: Обязаны ли все экземпляры View<'a> использовать одну и ту же конкретную длительность 'a?
Нет. 'a — параметр типа, а не глобальная именованная длительность. Разные места вызова могут подставить разные реальные области жизни, если каждая подстановка удовлетворяет ограничениям заимствования. Имя 'a в разных объявлениях также не обозначает одну общую программу-wide длительность.
Дополнительный вопрос 3: Можно ли заменить поле-ссылку на ссылку с 'static, чтобы структура стала универсальной?
Только если данные действительно гарантированно живут до завершения программы. Ограничение 'static не превращает обычную локальную строку в статическую и не продлевает её существование; оно лишь допускает ссылки, действительные весь срок программы, либо значения, не содержащие короткоживущих ссылок. Для обычного заимствованного входа параметр 'a точнее и менее ограничителен.