Lifetimes
Links:
- https://corrode.dev/blog/lifetimes/
- https://www.possiblerust.com/pattern/naming-your-lifetimes
- https://serde.rs/lifetimes.html
Время жизни
Метки времени жизни сообщают компилятору, как долго ссылка действительна.
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). Здесь мало что можно сделать, кроме как искать альтернативы, которые не требуют меток времени жизни.