Что такое нелексические времена жизни?

88

В Rust есть RFC, связанный с нелексическим временем жизни, который был одобрен для реализации в языке в течение длительного времени. В последнее время поддержка этой функции в Rust значительно улучшилась и считается полной.

У меня вопрос: что такое нелексическая жизнь?

Звездный Вратарь
источник

Ответы:

130

Проще всего понять, что такое нелексическое время жизни, если понять, что такое лексическое время жизни. В версиях 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никогда не используется ! Проблема в том, что заимствование scoresby scoreявляется лексическим - оно длится до конца блока, в котором оно содержится:

fn main() {
    let mut scores = vec![1, 2, 3]; //
    let score = &scores[0];         //
    scores.push(4);                 //
                                    // <-- score stops borrowing here
}

Нелексическое время жизни исправляет это, улучшая компилятор, чтобы понять этот уровень детализации. Теперь компилятор может более точно определить, когда требуется заимствование, и этот код будет компилироваться.

В нелексических жизнях замечательно то, что после включения о них никто никогда не будет думать . Он просто станет «тем, что делает 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.

Образец реальных задач, решаемых нелексическим временем жизни

Шепмастер
источник
11
Я думаю, что стоит подчеркнуть, что, что, возможно, противоречит интуиции, нелексическое время жизни не о времени жизни переменных, а о времени жизни заимствований. Или, иначе говоря, нелексическое время жизни - это декорреляция продолжительности жизни переменных и продолжительности заимствований ... если я не ошибаюсь? (но я не думаю, что NLL меняется при выполнении деструктора)
Матье М.
2
« Интересно, что некоторые хорошие шаблоны были разработаны из-за лексического времени жизни » - тогда я полагаю, существует риск того, что существование NLL может затруднить выявление будущих хороших шаблонов?
eggyal
1
@eggyal, это определенно возможно. Проектирование в рамках набора ограничений (даже если они произвольны!) Может привести к новым интересным проектам. Без этих ограничений мы могли бы прибегнуть к нашим существующим знаниям и шаблонам и никогда не научиться или исследовать, чтобы найти что-то новое. При этом, предположительно, кто-то подумает: «О, хэш вычисляется дважды, я могу это исправить», и API будет создан, но пользователям может быть труднее найти API в первую очередь. Я надеюсь, что такие инструменты, как clippy, помогут этим людям.
Shepmaster