Лайфтаймы
Мы уже знаем, что в Rust каждый объект имеет только одного владельца, но этот владелец может одалживать объект по ссылке в другие участки кода. При этом компилятор проверяет, что время жизни скоупа, одолжившего объект, не больше, чем время жизни владельца этого объекта.
Давайте рассмотрим такой код:
// Функция принимает две ссылки на строки и возвращает ту ссылку,
// которая указывает на более длинную строку.
fn take_longest(x: &str, y: &str) -> &str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let l = take_longest("aaa", "bbbb");
}
Попытка скомпилировать этот код провалится с ошибкой:
error[E0106]: missing lifetime specifier
help: this function’s return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y`
help: consider introducing a named lifetime parameter
Эта ошибка говорит, что компилятор не уверен в том, как соотносятся между собой время жизни владельца x, время жизни владельца y и время жизни переменной, в которую будет присвоен результат функции.
Чтобы стало понятнее, давайте взглянем на такой вариант использования функции take_longest:
let s1 = String::from("aaa");
let longest;
{
let s2 = String::from("bbbb");
longest = take_longest(s1.as_str(), s2.as_str());
}
Как мы видим, при таком сценарии, ссылка на строку, принадлежащую переменной s2, будет записана в переменную longest. Проблема в том, что переменная longest принадлежит скоупу, который “живёт” дольше, чем скоуп в который входит s2. А как мы сказали выше, компилятор проверяет, что ссылка на объект не “живёт” дольше, чем переменная, владеющая этим объектом.
Для решения этой проблемы необходимо явно указать, как должны соотноситься между собой время жизни владельцев объектов, на которые ссылаются аргументы функции, и время жизни переменной, в которую будет записан результат функции.
Такие отношения времени жизни указывают при помощи лайфтаймов (lifetime).
fn take_longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
Здесь в заголовке функции take_longest<'a> мы объявляем некий относительный лайфтайм 'a. Далее в каждой ссылке после знака & мы указываем, к какому лайфтайму принадлежит ссылка.
Запись лайфтаймов в заголовке функции take_longest можно прочитать так:
Cуществует некий лайфтайм
'aпроизвольной длины, который не короче времени жизни функцииtake_longest. Владельцы объектов, на которые ссылаютсяxиy, должны принадлежать к одному скоупу. А время жизни переменной, принимающей результат функции, не должно превышать время жизни этого скоупа.
После того, как мы задали лайфтаймы, следующая попытка использования функции take_longest приведёт к ошибке компиляции.
fn main() {
let s1 = String::from("aaa");
let longest;
{
let s2 = String::from("bbbb");
longest = take_longest(s1.as_str(), s2.as_str()); // does not live long enough
}
println!("The longest string is {}", longest);
}
’static лайфтайм
В Rust существует один “глобальный” лайфтайм — 'static. Принадлежность к этому лайфтайму означает, что время жизни объекта продлится от минимум текущего момента и до конца работы программы.
Например, константная ссылка на строковый литерал имеет лайфтайм 'static.
const text: &'static str = "some string";
fn main() {
println!("{text}");
}
Сложности работы с лайфтаймами
Как правило, для тех, кто начинает изучать Rust, именно лайфтаймы становятся наиболее сложным моментом. Поэтому если на первых порах вы испытываете сложности с проставлением лайфтаймов, то рекомендуется не заморачиваться со сложными связями между ссылками, а просто использовать .clone() для получения копии объекта.
Позднее, когда вы лучше освоитесь с языком, можно будет углубиться в лайфтаймы отдельно.