Разбор последствий: почему перечисление со ссылкой только в одном варианте всё равно должно иметь параметр времени жизни?
Параметр времени жизни относится ко всему типу перечисления, а не к отдельному значению или варианту. Rust должен заранее описать, сколько могут быть действительны все ссылки, которые потенциально хранятся в экземпляре этого типа, поэтому перечисление получает параметр времени жизни даже тогда, когда конкретное значение создано из варианта без ссылки.
Время жизни в Rust нужно для статической проверки заимствований без сборщика мусора. Типы, способные хранить ссылки, должны явно выражать связь между сроком существования контейнера и сроком действительности этих ссылок.
Такой подход позволяет компилятору проверять не только отдельное создание значения, но и передачу перечисления через функции, структуры, коллекции и возвращаемые значения. Проверка опирается на тип перечисления целиком, а не на предположение о том, какой вариант обычно используется.
Рассмотрим перечисление, один вариант которого хранит строковое представление без владения, а другой не содержит ссылок:
Если бы у Token не было параметра времени жизни, тип не сообщал бы, сколько может существовать ссылка внутри варианта Text. Это позволило бы рассматривать контейнер как не связанный с исходной строкой, хотя экземпляр фактически может её заимствовать.
Token<'a> означает: если экземпляр содержит ссылку, эта ссылка действительна как минимум в течение времени жизни 'a. Вариант End ссылки не содержит, но он всё равно является значением того же параметризованного типа Token<'a>.
Параметры типа принадлежат статическому типу значения. Во время выполнения Rust не меняет тип перечисления в зависимости от выбранного варианта: Token::Text и Token::End должны соответствовать одному объявлению Token<'a>. Поэтому время жизни нельзя объявить только у Text как у независимого локального свойства.
Для варианта без ссылки параметр 'a фактически не накладывает требования на данные этого конкретного значения. Однако сам тип всё равно сохраняет возможность содержать ссылку, и это важно при передаче значения в функции или хранении его в другой параметризованной структуре.
Время жизни не продлевает существование строки и не делает перечисление владеющим ею. Если выбран Text, исходная строка должна оставаться действительной столько, сколько требуется конкретному экземпляру Token<'a>. Если выбран End, ссылочного заимствования нет, но типовая форма остаётся той же.
Альтернатива — заменить заимствованный вариант на владеющий, например хранить String. Это устраняет параметр времени жизни, но может привести к копированию или выделению памяти. Заимствованный вариант обычно эффективнее, когда данные должны только временно просматриваться.
Парсер может возвращать токены, ссылающиеся на исходный входной буфер, и специальные маркеры конца потока. Вариант с &str избегает копирования лексем, а вариант End не требует данных.
Рассматривались два решения. Хранить String во всех токенах проще с точки зрения времени жизни, но это увеличивает стоимость аллокаций и копирования. Разделить токены на два независимых типа можно, однако усложняется API и обработка результата.
Выбран Token<'a>: параметр времени жизни честно связывает токены с входным буфером, а вариант End остаётся дешёвым. Результат нельзя использовать после уничтожения входных данных, зато компилятор гарантирует отсутствие висячей ссылки без runtime-проверок.
Означает ли Token<'a> наличие ссылки в каждом экземпляре?
Нет. Параметр времени жизни описывает возможности типа, а не гарантирует, что каждый вариант содержит ссылку. Экземпляр Token::End не заимствует данные, хотя его тип всё равно записывается как Token<'a> с некоторым конкретным временем жизни.
Может ли значение Token::Text пережить исходную строку, если оно фактически будет сопоставлено с End?
Нет. Проверка выполняется по возможной структуре типа, а не по предположению о будущем ветвлении. Пока значение имеет вариант Text, оно потенциально содержит ссылку, поэтому его использование ограничено временем жизни исходной строки; последующее сопоставление не отменяет уже существующих типовых гарантий.
Почему нельзя объявить время жизни только у варианта Text, сохранив Token без параметров?
Потому что параметр времени жизни должен участвовать в типах функций, полей и контейнеров, через которые передаётся всё перечисление. Если бы он был скрыт внутри варианта, сигнатура Token не выражала бы связь между сроком жизни перечисления и ссылкой внутри него. Параметр на уровне перечисления делает эту связь частью проверяемого контракта типа.