Собственно, это все правила, которым подчиняется lifetime elision. Если описанные выше случаи не применимы, то компилятор скажет "я не могу, у меня лапки". Отчасти именно из-за этого начинающим настолько затруднительно усваивать концепцию времён жизни: в простых случаях их писать не надо, поэтому ситуации, в которых ВЖ действительно нужно писать, уже не очень тривиальны. Какое это имеет отношение к анонимным временам жизни? Само непосредственное:
'_
можно указать вместо обобщённого параметра ВЖ, чтобы компилятор его вывел. Скажем, сигнатуру
first_and_second
можно написать и так:
fn first_and_second(arg: &(u32, u32, u32))- > (&'_ u32, &'_ u32)
. Кажется, что это ненужное добавление, но есть случаи, когда это необходимо (при условии, что программист всё ещё не хочет писать ВЖ явно). Рассмотрим вот такой код (несколько надуманный, но достаточно наглядный);
fn non_zero(slice: &[u32]) -> Box<dyn Iterator<Item = &u32>> {
Box::new(slice.iter().filter(|&&n| n != 0))
}
Этот код
не компилируется. Претензия компилятора сводится к несовпадению типов:
= note: expected `std::boxed::Box<(dyn std::iter::Iterator<Item = &u32> + 'static)>`
found `std::boxed::Box<dyn std::iter::Iterator<Item = &u32>>`
Да, тут есть ссылка в возвращаемом типе, и ей приписывается то же ВЖ, что и у
slice
, но мы не записали время жизни типа внутри коробки, то есть
dyn Iterator
. Компилятор предположил, что это владеющий тип и, следовательно, он имеет ВЖ
'static
, которое является более долгоживущим, чем любое другое ВЖ. Но это не так: возвращаемый итератор содержит ссылку на слайс и потому не может пережить этот слайс. Один из способов исправить ошибку — это ввести новый параметр ВЖ и указать, что возвращаемый тип столько же и живёт, но... Мы ленивые программисты и не хотим писать много, поэтому мы напишем ровно столько, чтобы заставить компилятор работать за нас:
fn non_zero(slice: &[u32]) -> Box<dyn Iterator<Item = &u32> + '_> { ... }
Да, разница действительно всего в 3 символа, не считая пробелов. С такой сигнатурой код уже
компилируется, потому теперь благодаря lifetime elision возвращаемому типу приписывается корректное ВЖ.
Но погодите-ка, это ещё не всё! Анонимное ВЖ можно также использовать и в блоке
impl
. Об этом написано в edition guide
здесь и
здесь. Порой в
impl
-блоке типа, параметризованного ВЖ, ничто не зависит от указанного ВЖ, поэтому вместо того, чтобы вводить явно ВЖ после
impl
и использовать его для типа, можно просто указать
'_
. Например, если у нас есть определение
struct StrWrap<'a>(&'a str);
, то можно написать блок методов так:
impl<'a> StrWrap<'a> { ...
, а можно — так:
impl StrWrap<'_>
Это работает и в том случае, если параметров ВЖ несколько: для
struct StrPair<'a, 'b: 'a>('a str, &'b str);
можно написать так;
impl StrPair<'_, '_> { ...
Если между ВЖ заданы отношения, то сгенерированные ВЖ будут также им удовлетворять.
Недостатки у такого способа тоже есть. Каждое использование
'_
в заголовке
impl
-блока создаёт новое ВЖ, что не всегда желательно. Например, в коде ниже
trait WithLifetime<'a> {}
impl WithLifetime<'_> for &str {}
реализация трейта эквивалентна
impl<'a, 'b> WithLifetime<'a> for &'b str {}
, а не
impl<'a> WithLifetime<'a> for &'a str {}
, как можно было подумать. Если требуется, чтобы двое ВЖ для методов было одинаковым, то их надо указывать (и вводить) явно — но только их, остальные можно заполнить анонимным ВЖ. Например, если у нас есть
struct Triple<'a, 'b, 'c>(&'a u32, &'b u32, &'c u32);
, то можно написать
impl
-блок так:
impl<'a> Triple<'a, 'a, '_> {}
Также, очевидно, в силу анонимности этих параметров на них нельзя напрямую ссылаться (но можно через псевдоним типа
Self
).
Подведём итоги:
* Анонимные времена жизни можно использовать в возращаемых типах и в заголовках
impl
-блоков
* В возвращаемых типах анонимные ВЖ заполняются с использованием нехитрых правил lifetime elision
* В
impl
-блоках анонимные ВЖ генерируют новые ВЖ на каждое упоминание
- В силу их анонимности их нельзя использовать напрямую