web-dev-qa-db-ja.com

非字句寿命とは何ですか?

Rustには RFC 非語彙のライフタイムに関連する 承認済み が長期にわたって言語に実装される予定です。 最近 、この機能のRustのサポートは大幅に改善されており、完全であると見なされています。

私の質問は:非字句寿命とは正確には何ですか?

58
Stargateur

lexical ライフタイムが何であるかを理解することにより、非字句的ライフタイムが何であるかを理解するのが最も簡単です。非字句のライフタイムが存在する前のRustのバージョンでは、このコードは失敗します:

fn main() {
    let mut scores = vec![1, 2, 3];
    let score = &scores[0];
    scores.Push(4);
}

Rustコンパイラは、scoresscore変数によって借用されていることを認識しているため、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 isnever used!問題は、scoresによるscoreの借用が lexical —それが含まれるブロックの終わりまで続くことです。

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

非語彙の有効期間は、コンパイラを強化してこのレベルの詳細を理解することでこれを修正します。コンパイラは、借用がいつ必要になるかをより正確に認識できるようになり、このコードがコンパイルされます。

非レキシカルライフタイムのすばらしい点は、一度有効にすると、誰も考えないであることです。それは単に「Rustがすること」になり、物事は(うまくいけば)うまくいくでしょう。

なぜレキシカルライフタイムが許可されたのですか?

Rustは、既知の安全なプログラムのコンパイルのみを許可することを目的としています。ただし、 不可能 正確に許可するonly安全なプログラムを許可し、安全でないプログラムを拒否します。そのため、Rustは保守的であるという誤りを犯します。安全なプログラムのいくつかは拒否されます。語彙の寿命はこの一例です。

字句の有効期間はmuchでした。これは、ブロックの知識は「自明」であり、データフローの知識はそれほど多くないため、コンパイラに実装するのが簡単でした。コンパイラは、 「中間レベルの中間表現」(MIR)を導入および利用するために書き直された である必要がありました。その後、ボローチェッカー(別名「borrowck」)を書き換えて、抽象構文ツリー(AST)の代わりにMIRを使用する必要がありました。その後、借用チェッカーのルールを細かくするために改良する必要がありました。

字句の寿命は常にプログラマの邪魔をするわけではありません。また、字句の寿命は、迷惑な場合でも、字句の寿命を回避する多くの方法があります。多くの場合、これには余分な中括弧またはブール値を追加する必要がありました。これにより、Rust 1.0が出荷され、非語彙の有効期間が実装される前に長年にわたって有用でした。

興味深いことに、特定のgoodパターンは、字句の寿命のために開発されました。私にとっての一番の例は、 entryパターン です。このコードは、非字句的ライフタイムの前に失敗し、それでコンパイルします:

fn example(mut map: HashMap<i32, i32>, key: i32) {
    match map.get_mut(&key) {
        Some(value) => *value += 1,
        None => {
            map.insert(key, 1);
        }
    }
}

ただし、このコードはキーのハッシュを2回計算するため、非効率的です。作成されたソリューションはbecauseレキシカルライフタイムで、より短く、より効率的です。

fn example(mut map: HashMap<i32, i32>, key: i32) {
    *map.entry(key).or_insert(0) += 1;
}

「非語彙寿命」という名前は私には正しく聞こえません

値のライフタイムは、値が特定のメモリアドレスに留まる期間です( 同じ構造体に値とその値への参照を格納できないのはなぜですか? 説明)。非字句寿命と呼ばれる機能はchange値の寿命を持たないため、寿命を非字句にすることはできません。これらの値の借用の追跡とチェックをより正確にするだけです。

この機能のより正確な名前は、「non-lexical borrows」です。一部のコンパイラ開発者は、基礎となる「MIRベースのボローク」を参照しています。

非語彙の有効期間は、「ユーザー向け」機能per seとなることを意図したものではありませんでした。彼らが不在から得る小さなペーパーカットのために、彼らはほとんど私たちの心の中で大きく成長しました。彼らの名前は主に内部開発の目的を意図しており、マーケティング目的でそれを変更することは決して優先事項ではありませんでした。

うん、でもどうやって使うの?

Rust 1.31(2018-12-06にリリース)では、Cargo.tomlのRust 2018エディションにオプトインする必要があります。

[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <[email protected]>"]
edition = "2018"

Rust 1.36の時点で、Rust 2015エディションは非語彙の有効期間も有効にします。

非字句的ライフタイムの現在の実装は、「移行モード」です。 NLLボローチェッカーがパスすると、コンパイルが続行されます。そうでない場合、以前のボローチェッカーが呼び出されます。古いボローチェッカーがコードを許可する場合、警告が出力され、コードがRustの将来のバージョンで壊れる可能性があり、更新する必要があることを通知します。

Rustの夜間バージョンでは、機能フラグを介して強制的な破損をオプトインできます。

#![feature(nll)]

コンパイラフラグ-Z poloniusを使用して、NLLの実験版をオプトインすることもできます。

非字句寿命によって解決される実際の問題のサンプル

84
Shepmaster