Lifetimes

Links:

Время жизни

Метки времени жизни сообщают компилятору, как долго ссылка действительна.

fn foo<'input>(bar: &'a str) {
    // ...
}

Имя меток может быть абсолютно любым.

Правила элизии (неявного использования) меток времени жизни

  • Каждая входная ссылка на функцию получает отдельное время жизни;
  • Если есть ровно одно входное время жизни, оно применяется ко всем выходным ссылкам;
  • Если есть несколько входных времен жизни, но одно из них — &self или &mut self, то время жизни self применяется ко всем выходным ссылкам.

Это означает, что вам нужно явно указывать времена жизни только в том случае, если у вас более одного входного времени жизни и ни одно из них не является &self или &mut self.

Заражение кода явными метками времени жизни

Если добавить метки времени жизни в структуру, например,

struct Foo {
    bar: String
}

оптимизировать в &str для предотвращения выделения памяти и превратить в:

struct Foo<'a> {
    bar: &'a str
}

то теперь нужно добавлять метки во все методы, которые используют эту структуру:

fn foo<'a>(foo: &'a Foo) {
    // ...
}

Пример явного использования меток времени

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

В данном коде компилятор не может предсказать, что данные, на которые указывают ссылки x и y, будут живыми к моменту выхода из функции (выдаст ошибку). При этом та или иная ссылка нужны, в зависимости от выполнения условия (та, где данные длиннее), а понять какая можно будет лишь во время запуска приложения. Поэтому необходимо уверить компилятор, что обе ссылки останутся рабочими до конца работы функции:

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Обход применения явных меток времени с помощью умных указателей Rc и Arc

Можно использовать умные указатели, Rc (подсчет ссылок) или Arc (атомарный подсчет ссылок), чтобы разделить владение данными. Таким образом, не нужно беспокоиться о явных временах жизни, сохраняя при этом затраты на клонирование данных близкими к нулю:

// Платим за выделение памяти лишь 1 раз
let hello = Rc::new("Hello".to_string());

// Дешёвая операция
let hello2 = hello.clone();

Пример с x и y можно переписать с применением Rc для примера:

use std::rc::Rc;

fn longest(x: Rc<String>, y: Rc<String>) -> Rc<String> {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

Причины для явного использования меток времени жизни

Причин в современном Rust всего две:

  • Существует узкое место в коде по производительности: Вы обнаружили медленный участок часто используемого кода, профилировали его и определили, что узкое место действительно вызвано выделением памяти. В этом случае имеет смысл использовать время жизни, чтобы избежать выделения памяти. (Альтернатива — реорганизовать ваш код, чтобы использовать лучший алгоритм и избежать «горячего пути» в первую очередь.)
  • Код библиотек, от которого зависит код Вашего приложения, требует аннотаций времени жизни (пример - html5ever). Здесь мало что можно сделать, кроме как искать альтернативы, которые не требуют меток времени жизни.