Как ковариантность параметра времени жизни пользовательского типа влияет на передачу значения с более долгим заимствованием туда, где ожидается более короткое?
Если пользовательский тип содержит ссылку в ковариантной позиции, его параметр времени жизни также является ковариантным. Поэтому значение типа с более долгим временем жизни можно использовать там, где ожидается тот же тип с более коротким временем жизни: Rust безопасно «укорачивает» заимствование.
Обратное преобразование невозможно: короткое заимствование нельзя представить как более долгоживущее. Ковариантность не продлевает жизнь данных, а только позволяет безопасно ограничить доступ к ним меньшим сроком.
В Rust время жизни является частью статической системы типов и проверяется компилятором до запуска программы. Для этого системе нужно понимать не только отдельные ссылки, но и то, как изменение времени жизни ссылки влияет на тип, содержащий эту ссылку.
Правила вариантности решают эту задачу. Они позволяют формально определить, когда один параметризованный тип можно безопасно использовать вместо другого, не добавляя накладных расходов во время выполнения.
Пусть структура хранит ссылку с параметром времени жизни. На уровне значения ссылка с долгим временем жизни безопасно может быть ограничена коротким сроком: после такого ограничения программа просто обязуется не использовать её дольше.
Но это преобразование должно распространяться и на содержащий ссылку тип. Если Rust не учитывал бы ковариантность пользовательских типов, пришлось бы создавать лишние обёртки или вручную перестраивать значения даже в безопасных случаях. Если бы он разрешал обратное преобразование, возникла бы возможность получить ссылку после уничтожения исходных данных.
Рассмотрим тип View<'a>, содержащий поле &'a str. Поскольку обычная неизменяемая ссылка ковариантна по своему времени жизни, View<'a> тоже является ковариантным по 'a.
Вызов может выбрать короткое 'short по второму аргументу. Ссылка long живёт дольше, поэтому её можно безопасно использовать как &'short str, а значение View создаётся с соответствующим укороченным параметром времени жизни.
Важно различать время жизни и вариантность по типу значения. Например, изменяемая ссылка &'a mut T ковариантна по 'a, поэтому её заимствование можно укоротить, но инвариантна по T, поскольку через неё разрешена запись. Иначе можно было бы записать значение неподходящего типа через ссылку, выданную как ссылку на более общий тип.
Вариантность пользовательского типа выводится по тому, как параметр используется в его полях. Поле &'a str даёт ковариантность по 'a, тогда как инвариантные конструкции, например Cell<&'a str>, могут сделать параметр времени жизни инвариантным. В таком случае автоматическое преобразование Container<'long> в Container<'short> запрещается.
Ковариантность действует только в направлении укорочения: если 'long дольше 'short, допустимо подставить View<'long> там, где требуется View<'short>. Она не меняет фактическое время жизни source, не перемещает данные и не создаёт дополнительную гарантию существования объекта.
Представим API, который принимает представление текста только на время обработки запроса. У вызывающего кода есть структура с ссылкой на буфер, живущий дольше всего запроса.
Вариант с ковариантным представлением позволяет передать эту структуру напрямую. Плюсы — отсутствие копирования строки, дополнительных обёрток и ручного преобразования типов; минус — API всё равно должен гарантировать, что не сохранит представление дольше объявленного короткого времени жизни.
Альтернатива — передавать саму ссылку отдельно и каждый раз создавать временное представление. Это может сделать границы заимствования очевиднее, но увеличивает шаблонный код и усложняет интерфейс. Обычно выбирают пользовательский тип с ковариантной ссылкой: компилятор сам проверяет укорочение, а данные остаются во владении исходного буфера.
Если же представление содержит внутреннюю изменяемую или интериорно-изменяемую часть, его вариантность может быть ограничена. Тогда попытка передать долгоживущее значение как короткоживущее должна быть отклонена, потому что тип может не только читать ссылку, но и косвенно менять значение, связанное с ней.
1. Означает ли ковариантность, что можно передать короткое время жизни вместо долгого?
Нет. Ковариантность разрешает только направление от более долгого времени жизни к более короткому. Если функция требует тип, способный оставаться действительным дольше, короткая ссылка не удовлетворяет этому требованию: после завершения её области действия значение стало бы недействительным.
2. Продлевает ли преобразование View<'long> в View<'short> жизнь исходной ссылки?
Нет. Оно создаёт лишь более ограниченный типовой взгляд на уже существующую ссылку. Исходные данные по-прежнему уничтожаются в своей обычной точке, а компилятор запрещает использовать преобразованное значение после окончания 'short.
3. Всегда ли структура со ссылкой ковариантна по времени жизни?
Нет. Вариантность определяется всеми позициями, где используется параметр. Если 'a встречается внутри инвариантного типа, например Cell<&'a str>, структура может стать инвариантной по 'a; тогда безопасное на первый взгляд укорочение через подстановку типов не разрешается. Это ограничение связано не с самой аннотацией времени жизни, а с тем, какие операции предоставляет содержащий тип.