Почему при подстановке типов функций время жизни аргумента-ссылки проверяется контравариантно?
Время жизни аргумента функции проверяется контравариантно, потому что функция, принимающая ссылку на более короткое время, безопасно может заменить функцию, от которой требуется принимать ссылку на более длинное время. Получив длинную ссылку, такая функция просто использует её в пределах более короткого допустимого заимствования.
Обратная подстановка небезопасна: функция, рассчитанная только на ссылку, живущую достаточно долго, может получить ссылку, которая исчезнет раньше.
В Rust времена жизни являются частью статической системы типов, которая должна гарантировать безопасность заимствований без сборщика мусора и без продления объектов во время выполнения. Для этого компилятору нужно проверять не только отдельные ссылки, но и возможность безопасной подстановки сложных типов.
Правила вариантности решают именно эту задачу: они определяют, как изменение параметра типа или времени жизни влияет на совместимость исходного типа с другим типом. Для функций направление проверки меняется для аргументов, поскольку функция является потребителем своих параметров.
Пусть одна функция должна принимать ссылку, действительную в течение длительного времени, а другая фактически умеет работать со ссылкой только в течение более короткого периода. На первый взгляд кажется, что более короткое время жизни делает вторую функцию менее подходящей.
Однако длинную ссылку можно безопасно использовать как короткую: заимствование разрешено завершить раньше. Поэтому функция, принимающая короткую ссылку, обычно предъявляет более слабое требование к вызывающему коду.
Опасна обратная ситуация. Если вместо функции, способной принять короткую ссылку, передать функцию, требующую длинную, она может сохранить или использовать ссылку после уничтожения исходного значения.
Пусть 'long означает время жизни, которое длится дольше 'short. Ссылка &'long T может быть укорочена до &'short T, но &'short T нельзя безопасно превратить в &'long T.
Для функции направление меняется:
fn(&'short T) может принять &'long T, сократив его использование до 'short;fn(&'long T);fn(&'long T) не может заменить fn(&'short T), поскольку короткая ссылка не удовлетворяет требованию 'long.Минимальная иллюстрация:
&str в параметре any_lifetime означает, что функция может работать с заимствованием любого подходящего времени жизни. Поэтому она подходит для вызова с 'static. Обратная подстановка невозможна: функция only_static не обязана корректно работать с временной ссылкой.
Это правило касается именно позиции параметра функции. Для результата функции направление обычно противоположное: возвращаемое значение является производимым функцией результатом, поэтому время жизни результата проверяется ковариантно. При анализе реальных типов также нужно учитывать другие свойства, например изменяемость ссылки и ограничения самих типов.
Аннотация времени жизни не меняет длительность объекта и не заставляет функцию хранить ссылку дольше. Она только описывает допустимую связь между временем жизни аргумента и требованиями типа функции.
Представим обработчик входного буфера. Буфер создаётся вызывающим кодом, передаётся обработчику только на время вызова и после этого может быть изменён или уничтожен.
Возможны три подхода:
'static. Это слишком жёстко: временный буфер нельзя будет передать без копирования.String или Vec<u8>. Это упрощает управление временем жизни, но может потребовать ненужного выделения памяти и передачи владения.Практически выбирают третий вариант, если обработчику достаточно синхронного доступа к данным. Контравариантность позволяет использовать более универсальный обработчик там, где контекст предоставляет ссылку с конкретным, более длинным временем жизни. Если обработчик должен сохранить данные, временную ссылку заменяют принадлежащим значением или явно проектируют другой механизм владения.
1. Дополнительный вопрос: означает ли контравариантность, что функция может сама продлить время жизни аргумента?
Нет. Контравариантность разрешает только безопасно использовать более длинную ссылку в контексте более короткого заимствования. Функция не получает права вернуть ссылку с продлённым временем жизни, сохранить её после разрешённого срока или сделать её 'static.
Продолжительность заимствования определяется правилами владения и местом использования ссылки. Тип функции лишь проверяет, что её требования не строже возможностей вызывающего кода.
2. Дополнительный вопрос: почему обратная подстановка может привести к висячей ссылке?
Предположим, вызывающий код располагает ссылкой, действительной только до конца текущего блока, но передаёт её функции, которая по типу может хранить ссылку до 'long. Если разрешить такую подстановку, функция получила бы право использовать ссылку после уничтожения исходного значения.
Именно поэтому функция, требующая 'long, не может притворяться функцией, принимающей 'short. Контравариантность запрещает подмену, которая расширила бы допустимый срок использования входной ссылки.
3. Дополнительный вопрос: распространяется ли это правило на весь тип функции без учёта других параметров?
Нет. Совместимость проверяется по всем компонентам типа функции. Времена жизни аргументов анализируются с учётом контравариантности, но типы значений, изменяемость ссылок, количество параметров и тип результата также должны соответствовать правилам Rust.
Например, подходящее время жизни не сделает совместимыми функции, различающиеся типом аргумента String и Vec<u8>. Кроме того, универсальная функция, способная принять ссылку любого времени жизни, может быть совместима с более узким требованием, но функция, рассчитанная на один конкретный срок, не становится универсальной автоматически.