Проще всего понять, что такое нелексическое время жизни, если понять, что такое лексическое время жизни. В версиях Rust до нелексического времени жизни этот код завершится ошибкой:
fn main() {
let mut scores = vec![1, 2, 3];
let score = &scores[0];
scores.push(4);
}
Компилятор Rust видит, что scores
это заимствовано score
переменной, поэтому он запрещает дальнейшее изменение scores
:
error[E0502]: cannot borrow `scores` as mutable because it is also borrowed as immutable
--> src/main.rs:4:5
|
3 | let score = &scores[0];
| ------ immutable borrow occurs here
4 | scores.push(4);
| ^^^^^^ mutable borrow occurs here
5 | }
| - immutable borrow ends here
Тем не менее, человек может тривиальный видеть , что этот пример является слишком консервативным: score
никогда не используется ! Проблема в том, что заимствование scores
by score
является лексическим - оно длится до конца блока, в котором оно содержится:
fn main() {
let mut scores = vec![1, 2, 3];
let score = &scores[0];
scores.push(4);
}
Нелексическое время жизни исправляет это, улучшая компилятор, чтобы понять этот уровень детализации. Теперь компилятор может более точно определить, когда требуется заимствование, и этот код будет компилироваться.
В нелексических жизнях замечательно то, что после включения о них никто никогда не будет думать . Он просто станет «тем, что делает Rust», и все (надеюсь) будет работать.
Почему разрешено лексическое время жизни?
Rust предназначен для компиляции только заведомо безопасных программ. Однако нельзя точно разрешить только безопасные программы и отклонить небезопасные. С этой целью Rust ошибается в сторону консервативности: некоторые безопасные программы отклоняются. Лексическое время жизни - один из примеров этого.
Лексическое время жизни было намного проще реализовать в компиляторе, потому что знание блоков «тривиально», а знание потока данных - меньше. Компилятор нужно было переписать, чтобы ввести и использовать «промежуточное представление среднего уровня» (MIR) . Затем пришлось переписать средство проверки заимствований (также известное как «заимствование»), чтобы использовать MIR вместо абстрактного синтаксического дерева (AST). Затем правила проверки заемных средств нужно было усовершенствовать, чтобы сделать их более детальными.
Лексическое время жизни не всегда мешает программисту, и есть много способов обойти лексическое время жизни, когда они это делают, даже если они раздражают. Во многих случаях для этого требовалось добавление дополнительных фигурных скобок или логического значения. Это позволило Rust 1.0 появиться и быть полезным в течение многих лет, прежде чем были реализованы нелексические времена жизни.
Интересно, что некоторые хорошие шаблоны были разработаны из-за лексической жизни. Для меня лучший пример - это entry
узор . Этот код выходит из строя до нелексического времени жизни и компилируется с ним:
fn example(mut map: HashMap<i32, i32>, key: i32) {
match map.get_mut(&key) {
Some(value) => *value += 1,
None => {
map.insert(key, 1);
}
}
}
Однако этот код неэффективен, потому что он дважды вычисляет хеш ключа. Решение, которое было создано из- за лексического времени жизни, короче и эффективнее:
fn example(mut map: HashMap<i32, i32>, key: i32) {
*map.entry(key).or_insert(0) += 1;
}
Название "нелексические времена жизни" мне не подходит
Время жизни значения - это промежуток времени, в течение которого значение остается по определенному адресу памяти (см. Почему я не могу сохранить значение и ссылку на это значение в одной структуре? Для более подробного объяснения). Функция, известная как нелексическое время жизни, не изменяет время жизни каких-либо значений, поэтому не может сделать время жизни нелексическим. Это только делает более точным отслеживание и проверку заимствований этих значений.
Более точное название функции может быть «нелексические заимствования ». Некоторые разработчики компиляторов ссылаются на основной «заимствование на основе MIR».
Нелексические сроки жизни никогда не предназначались как функция, ориентированная на пользователя, как таковая . В основном они стали большими в нашем сознании из-за маленьких вырезок, которые мы получаем из-за их отсутствия. Их название в основном предназначалось для внутреннего развития, и изменение его в маркетинговых целях никогда не было приоритетом.
Да, но как мне это использовать?
В Rust 1.31 (выпущен 06.12.2018) вам необходимо подписаться на версию Rust 2018 в вашем Cargo.toml:
[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"
Начиная с Rust 1.36, версия Rust 2015 также поддерживает нелексические времена жизни.
Текущая реализация нелексических времен жизни находится в «режиме миграции». Если проверка заимствований NLL прошла успешно, компиляция продолжается. Если это не так, вызывается предыдущая программа проверки заимствований. Если старая программа проверки заимствований разрешает код, выводится предупреждение, информирующее вас о том, что ваш код, вероятно, сломается в будущей версии Rust и его следует обновить.
В ночных версиях Rust вы можете отказаться от принудительной поломки с помощью флага функции:
#![feature(nll)]
Вы даже можете подписаться на экспериментальную версию NLL, используя флаг компилятора -Z polonius
.
Образец реальных задач, решаемых нелексическим временем жизни