非字句の存続期間とは何ですか?


回答:


139

これは、非語彙的寿命は何を理解することであるかを理解するのが最も簡単だ字句寿命です。非字句の有効期間が存在する前のバージョンの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はありません。問題は、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)を導入して利用するには、コンパイラを書き直す必要がありました。次に、抽象構文木(AST)の代わりにMIRを使用するように、借用チェッカー(別名「borrowck」)を書き直す必要がありました。次に、借用チェッカーのルールを洗練して、よりきめ細かくする必要がありました。

字句の存続期間が常にプログラマーの邪魔になるとは限りません。また、字句の存続期間が煩わしい場合でも、回避する方法はたくさんあります。多くの場合、これには中括弧またはブール値の追加が含まれていました。これにより、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);
        }
    }
}

ただし、このコードはキーのハッシュを2回計算するため、非効率的です。字句の有効期間のために作成されソリューションは、より短く、より効率的です。

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

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

値の有効期間は、値が特定のメモリアドレスにとどまる期間です(詳細については、値とその値への参照を同じ構造体に格納できないのなぜですか?参照してください)。非字句の有効期間と呼ばれる機能は、値の有効期間を変更しないため、有効期間を非字句にすることはできません。これらの値の借用の追跡とチェックをより正確にするだけです。

この機能のより正確な名前は、「非字句借用」である可能性があります。一部のコンパイラ開発者は、基礎となる「MIRベースの借用」を参照しています。

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

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

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

[package]
name = "foo"
version = "0.0.1"
authors = ["An Devloper <an.devloper@example.com>"]
edition = "2018"

Rust 1.36の時点で、Rust2015エディションは非字句のライフタイムも有効にします。

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

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

#![feature(nll)]

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

非語彙の存続期間によって解決される実際の問題のサンプル


12
おそらく直感に反して、非語彙の寿命は変数の寿命ではなく、借入の寿命についてであることを強調する価値があると思います。または、別の言い方をすれば、非字句寿命とは、変数の寿命を借用の寿命から無相関化することです...私が間違っていない限り?(しかし、デストラクタが実行されたときにNLLが変わるとは思わない)
MatthieuM.18年

2
興味深いことに、語彙の寿命のために特定の良いパターンが開発されました」—それでは、NLLの存在により、将来の良いパターンを特定するのがはるかに難しくなるリスクがあると思いますか?
eggyal

1
@eggyalそれは確かに可能性です。一連の制約内で設計すると(任意であっても!)、新しい興味深い設計につながる可能性があります。これらの制約がなければ、既存の知識とパターンに頼り、新しいものを見つけるために学習したり探索したりすることはありません。そうは言っても、おそらく誰かが「ハッシュが2回計算されているので、修正できます」と考えてAPIを作成しますが、そもそもユーザーがAPIを見つけるのは難しいかもしれません。clippyのようなツールがそれらの人々を助けることを願っています。
シェップマスター
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.