Разберите, почему явная аннотация времени жизни у локальной ссылочной переменной иногда приводит к ошибке, которой не возникает при выводе времени жизни.
Явная аннотация времени жизни у локальной ссылки задаёт обязательное ограничение, а не просто документирует тип. Если указать слишком длинное время жизни, Rust обязан считать заимствование действительным весь этот период, даже когда фактически ссылка уже не используется.
При выводе типа компилятор может выбрать более короткое время жизни благодаря NLL — анализу непересекающихся фактических интервалов использования. Поэтому избыточная аннотация локальной ссылки способна продлить заимствование и заблокировать последующую операцию.
В модели владения Rust время жизни нужно не для управления памятью во время выполнения, а для статической проверки действительности ссылок. Явные аннотации особенно важны в публичных сигнатурах, где они описывают связи между входными и выходными ссылками.
Для локальных переменных компилятор обычно имеет больше информации и может вывести подходящую длительность заимствования. Развитие анализа непересекающихся лексических времён жизни позволило учитывать последнее фактическое использование ссылки, но явное ограничение типа по-прежнему нельзя произвольно ослабить.
Пусть функция получает изменяемую ссылку и временно создаёт из неё неизменяемый просмотр. Если просмотр объявлен с временем жизни всего входного заимствования, Rust должен сохранять неизменяемое заимствование до конца этого периода.
Тогда последующая изменяющая операция над исходным объектом становится запрещённой: изменяемое и неизменяемое заимствования не могут пересекаться. При выводе времени жизни просмотр мог бы завершиться сразу после последнего чтения, и конфликт не возник бы.
Рассмотрим пример:
Аннотация &'a str требует, чтобы ссылка view была действительна в течение 'a. Поскольку 'a связано с входной ссылкой функции, созданное неизменяемое заимствование рассматривается как продолжающееся до конца соответствующего срока.
После println! переменная view больше логически не нужна, но явный тип уже зафиксировал более сильное требование. Поэтому вызов text.push конфликтует с ещё действующим неизменяемым заимствованием.
Если написать let view: &str = &*text;, время жизни ссылки выводится из контекста. Оно может быть ограничено последним использованием view, и после println! заимствование завершается. Это не означает, что аннотация продлевает объект или изменяет его физическое время существования: она лишь ограничивает допустимые варианты статического анализа.
Важно различать действительность ссылки и длительность конкретного заимствования. Объект, на который указывает ссылка, должен жить достаточно долго, но само заимствование может быть короче. Явный параметр времени жизни в локальном типе способен потребовать более длинный вариант, если такой вариант допустим; если он недопустим, компилятор выдаст ошибку.
Практическое правило: аннотации времени жизни обычно нужны в сигнатурах, структурах и других местах, где требуется выразить связь типов. В локальных типах их следует добавлять только при необходимости, потому что они могут сделать ограничения строже и помешать NLL выбрать минимальный безопасный срок.
В коде обработчика строк разработчик явно аннотировал локальное представление временем жизни входного параметра, чтобы «сделать связь очевидной». После чтения представления обработчик должен был изменить исходную строку, но компилятор сообщил о конфликте заимствований.
Рассматривались три варианта:
Выбран третий вариант, поскольку связь времени жизни уже выражалась сигнатурой функции, а локальная аннотация не добавляла полезного контракта. В результате NLL завершал неизменяемое заимствование после последнего чтения, и последующая модификация становилась допустимой.
1. Обозначает ли &'a T точную длительность заимствования, равную 'a?
Нет. Это означает, что ссылка гарантированно действительна как минимум в требуемом контексте 'a. В общем случае время жизни — это ограничение валидности и отношений между ссылками, а не измерение, которое обязано совпасть с фактическим последним использованием.
Однако в явной аннотации локального типа такой контракт может заставить компилятор рассматривать конкретное заимствование как более длительное. Поэтому нужно отличать семантику типа ссылки от оптимального вывода области фактического использования.
2. Почему компилятор не всегда просто сокращает 'a, если ссылка больше не используется?
Потому что параметр 'a уже является частью типа и может участвовать в других ограничениях. Если локальная переменная имеет тип &'a T, сокращение этого параметра изменило бы заявленный тип переменной, а не только область её использования.
NLL может сокращать неявно выведенное заимствование, но не отменяет явное требование, что ссылка должна соответствовать конкретному времени жизни. Чтобы разрешить конфликт, обычно убирают ненужную аннотацию или ограничивают область переменной блоком.
3. Поможет ли заменить именованное время жизни на '_ в локальном типе?
Да, если цель — вернуть вывод времени жизни компилятору. Заполнитель '_ не означает конкретное длинное время жизни: в таком контексте Rust выводит подходящее значение по окружающим ограничениям.
Это отличается от записи &'a T, где используется уже объявленный параметр и тем самым задаётся определённая связь. Но '_ не устраняет фундаментальные конфликты: если другие условия требуют длительного заимствования, ошибка всё равно сохранится.