Вы писали функции, принимающие ссылки. В какой-то момент вы попробовали вернуть ссылку - и Rust
остановил вас с ошибкой про lifetime specifier. Вы добавили 'a в сигнатуру, и либо стало
лучше, либо нет, но в обоих случаях вы не понимали почему. Распространённый вывод: 'a управляет
тем, как долго что-то живёт. Это не так.
Договор аренды
Представьте: вы организуете мероприятие. Вы арендовали зал с семи до полуночи. На основании этого договора вы выдаёте пропуска. Каждый пропуск действителен ровно на срок аренды - не дольше. Когда аренда заканчивается и зал возвращается владельцу, все пропуска становятся недействительными. Договор не определяет, как долго существует здание; он определяет, как долго действует ваше право на него. Пропуска не продлевают аренду. Они на неё ссылаются.
Теперь представьте два зала - основной и резервный. Оба доступны с начала мероприятия, но основной арендован до десяти, резервный - до полуночи. Какой использовать - решат по числу гостей. Вы выдаёте пропуск команде обслуживания. Пропуск должен работать в любом случае - значит, он действителен только пока открыты оба варианта, то есть до десяти. Не потому что так написано на пропуске, а потому что это единственный период, когда результат гарантированно верен.
Именно это делает аннотация 'a в Rust: фиксирует, что возвращаемая ссылка действительна
ровно столько, сколько живут оба аргумента. Не дольше.
Дата на пропуске
Распространённое заблуждение: 'a - это длительность, и написание её в коде заставляет Rust
держать что-то живым дольше. Это неверно. Оно формируется потому, что добавление 'a иногда
убирает ошибку компилятора - и это ощущается как подтверждение. Но это совпадение, а не причина.
fn active_label(current: &str, default: &str) -> &str {
if current.is_empty() { default } else { current }
}
error[E0106]: missing lifetime specifier
--> src/main.rs:1:46
|
1 | fn active_label(current: &str, default: &str) -> &str {
| ---- ---- ^ expected named lifetime parameter
|
= help: this function's return type contains a borrowed value, but the signature
does not say whether it is borrowed from `current` or `default`
Компилятор не спрашивает, как долго что-то должно жить. Он спрашивает: до какого момента
действителен этот пропуск? Возвращаемый &str указывает на данные одного из двух аргументов -
но без аннотации компилятор не знает какого, а значит не может определить срок действия
пропуска и проверить что он не истечёт раньше чем будет использован.
Пометка на пропуске
fn active_label<'a>(
current: &'a str, // основной зал - до десяти
default: &'a str, // резервный зал - до полуночи
) -> &'a str { // пропуск действителен пока оба открыты
if current.is_empty() { default } else { current }
}
'a не продлевает жизнь current или default. Это пометка: возвращаемая ссылка действительна
в пределах пересечения их сроков - пока живы оба. Реальный срок определяется областями
видимости в вызывающем коде.
Теперь компилятор может проверить:
fn main() {
let label;
{
let current = String::from("beta"); // основной зал открывается
label = active_label(¤t, "stable"); // пропуск выдан - действителен пока оба открыты
} // основной зал закрылся - пропуск истёк
println!("{}", label); // пропуск уже недействителен
}
error[E0597]: `current` does not live long enough
--> src/main.rs:9:28
|
9 | label = active_label(¤t, "stable");
| ^^^^^^^^ borrowed value does not live long enough
10 | }
| - `current` dropped here while still borrowed
11 | println!("{}", label);
| ----- borrow later used here
Аннотация сделала зависимость видимой. Компилятор нашёл нарушение. Исправление - не менять
аннотацию, а использовать label пока оба зала ещё открыты:
fn main() {
let current = String::from("beta"); // основной зал открывается
let label = active_label(¤t, "stable"); // пропуск выдан - оба зала ещё открыты
println!("{}", label); // используем пока оба открыты
} // залы закрываются - пропуск истекает, но уже не нужен
Что теперь читается иначе
Когда видите 'a в сигнатуре функции - читайте не как таймер, а как условие: возвращаемая
ссылка действительна пока живы оба аргумента.
Стоит знать ещё одно: если нужно вернуть ссылку только из одного конкретного аргумента -
можно дать им разные аннотации: 'a и 'b. Тогда возвращаемая ссылка будет привязана именно
к тому аргументу, от которого зависит. Это уже точечный контроль, а не пересечение.