Перед вами функция, принимающая callback, который возвращает заимствование из своего аргумента. Объясните, какую гарантию здесь даёт for<'a>:
fn identity<'a>(value: &'a str) -> &'a str {
value
}
fn apply(
callback: impl for<'a> Fn(&'a str) -> &'a str,
text: &str,
) -> &str {
callback(text)
}
fn main() {
let text = String::from("ready");
println!("{}", apply(identity, &text));
}
for<'a> означает, что callback должен работать с любой временем жизни 'a, выбранным отдельно для конкретного вызова. Он может вернуть ссылку, связанную с переданным аргументом, но не ссылку на локальное значение callback или на данные с более коротким временем жизни.
Это не продлевает время жизни данных и не передаёт владение строкой: apply возвращает заимствование, действительное не дольше text.
Rust стремится обеспечивать безопасность памяти без сборщика мусора. Для этого компилятор проверяет не только факт заимствования, но и связь между временем жизни входных и выходных ссылок.
Обычного параметра lifetime бывает недостаточно для callback-типов: он может связать аргумент callback с одной заранее выбранной областью. Higher-ranked trait bound, записанный как for<'a>, позволяет выразить требование «для любого времени жизни», что важно для обобщённых функций, работающих с временными заимствованиями.
apply не владеет text и не должен требовать от callback конкретного времени жизни заранее. При каждом вызове ему нужно передать ссылку на фактический объект и получить ссылку, безопасную до конца использования этого объекта.
Если callback мог бы вернуть ссылку на локальное значение или на данные, живущие меньше входного аргумента, после возврата возникла бы висячая ссылка. Если же ограничить callback одной конкретной областью, функция потеряла бы обобщённость и не смогла бы корректно работать с заимствованиями разной длительности.
Запись for<'a> Fn(&'a str) -> &'a str читается так: существует реализация Fn для каждого 'a, и при передаче &'a str callback возвращает &'a str. В apply компилятор выбирает 'a, соответствующее времени жизни ссылки text в данном вызове.
Связь одинаковых 'a на входе и выходе запрещает callback вернуть ссылку с более коротким временем жизни. При этом callback может вернуть сам вход, как identity, либо другое заимствование, если его lifetime гарантированно не короче входного.
Без for<'a> ограничение выглядело бы примерно так:
Здесь callback связывается с конкретным 'a, выбранным для вызова apply_narrow. Вариант с for<'a> требует более сильного свойства: callback должен быть пригоден для любого 'a, а не только для одного конкретного времени жизни.
for<'a> не делает результат независимым от входа и не превращает &str в String. Если исходная строка уничтожена, возвращённая ссылка также перестаёт быть допустимой. Кроме того, callback не сможет вернуть ссылку на своё локальное значение, поскольку локальная переменная уничтожается при выходе из callback.
Представим обработку нескольких буферов: callback должен нормализовать представление и вернуть срез исходного буфера. Один вариант — передавать String и возвращать новую String; это проще с точки зрения lifetime, но требует дополнительных выделений и копирования.
Второй вариант — использовать callback, возвращающий &str, но связать его с одним внешним lifetime. Такой API может оказаться чрезмерно ограниченным, когда разные вызовы работают с буферами, живущими в разных областях.
Выбранный вариант с for<'a> позволяет каждому вызову получить собственное заимствование:
Результат не требует копирования и не может пережить исходный String. Цена решения — callback обязан соблюдать строгую связь результата с входом; для преобразования, создающего независимый результат, нужно возвращать владеющий тип, например String.
for<'a> выбор одного глобального времени жизни для всех вызовов?Нет. Это квантификация по lifetime: каждый вызов может использовать свой 'a. В цикле callback может последовательно получать ссылки на разные буферы, и время жизни каждой ссылки проверяется отдельно.
Только если lifetime этой ссылки гарантированно подходит для любого входного 'a, что обычно невозможно для обычного захваченного значения. Требование &'a str -> &'a str означает, что результат должен быть корректен именно относительно текущего входа; безопасный и типичный вариант — вернуть часть входного значения.
for<'a> жизнь временной строки, переданной в callback?Нет. Он лишь описывает допустимую связь lifetime. Если передать ссылку на временное значение, она останется действительной только в разрешённой области жизни этого временного значения; сохранить её дольше через возвращаемый результат нельзя. Для результата, который должен пережить исходные данные, требуется владение, например возврат String, а не &str.