THINK

Rust覚書11: ライフタイム

3/5/2020

ライフタイムを使用した有効な参照: validating references with lifetimes

Rustの全ての参照には、ライフタイムが存在する

これは、参照が有効な範囲のことである

複数の型が可能な場合は、肩にアノテーションを付ける必要がるのと同様に、 参照のライフタイムがいくつかの異なる方法で関連付けられる場合、 ライフタイムにアノテーションを付ける必要がある

Rustでは、実行時に使用される実際の参照が確実に有効になるように、 一般的なライフタイムパラメーターを使用して、関係に注釈を付ける必要がある

ライフタイムの概念は 他のプログラミング言語のツールとは多少異なり、 ほぼ間違いなくRustのもっとも特徴的な機能である

ライフタイムでのダングリング参照の防止: preventing dangling references with lifetimes

ライフタイムの主な目的は、プログラムが参照する予定のデータ以外のデータを参照する原因となる ダングリング参照を防ぐことである

{
    let r;

    {
        let x = 5;
        r = &x;
    }

    println!("r: {}", r);
}
  1. 外部スコープは初期値無しでrという名前の変数を宣言し、 内部スコープは5という初期値でxという名前の変数を宣言する
  2. 内部スコープ内でrの値をxへの参照として設定しようとしてる
  3. 内部スコープが終了し、rの値を出力しようとしている

このコードは、使用しようとする前にrが参照している値が範囲外になるため、 コンパイルされずエラーとなる

ボロー(借用)チェッカー: the borrow checker

Rustコンパイラには、スコープを比較して、全ての借用が有効かどうかを判断する ボローチェッカーがある

関数の一般的なライフタイム: generic lifetimes in functions

次の処理はコンパイルエラーとなる

fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("The longest string is {}", result);
}
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

ライフタイムアノテーションの構文: lifetime annotation syntax

ライフタイムアノテーションは、参照の有効期間を変更しない

シグネチャがジェネリック型を指定するときに、 関数が任意の型を受け入れることができるように、関数はジェネリックライフタイムパラメーターを指定することにより、 任意の有効期間をもつ参照を受け入れることができる

ライフタイムアノテーションは、ライフタイムに影響を与えること無く、 複数の参照のライフタイムの相互関係を記述する

ライフタイムアノテーションの構文はやや変わっている

ライフタイムパラメーターはアポストロフィ(')で始まる必要があり、 通常はジェネリック型のように全て小文字で非常にに短くなっている

&i32        // a reference
&'a i32     // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

ライフタイムアノテーションは、それ自体ではあまり意味がない

アノテーションは、複数の参照のジェネリックライフタイムパラメーターが 互いにどのように関連するかをRustに伝えるためのものである

関数シグネチャのライフタイムアノテーション: lifetime annotation in function signatures

ジェネリック型パラメーターと同様に、 関数名とパラメーターリストの間の山括弧内にジェネリックライフタイムパラメーターを宣言する必要がある

このシグネチャで表現したい制約は、パラメーター内の全ての参照と、 戻り値の値のライフタイムが同じでなければならないというこどである

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

関数シグネチャは、Rustにあるライフタイム'aに対して、 少なくともライフタイム'aと同じ長さの文字列スライスである2つのパラメーターを取ることを伝える

また、関数シグネチャは関数から返された文字列スライスが少なくともライフタイム'aの間存続することを Rustに伝える

この関数シグネチャでライフタイムパラメーターを指定する場合、 渡される値または返される値のライフタイムを変更しないことに注意

関数のライフタイムにアノテーションを付ける場合、 アノテーションは関数の本体にではなく、関数のシグネチャに入れられる

関数がその関数の外部のコードへの参照またはそのコードからの参照を持っている場合、 Rustがそれ自体でパラメーターの寿命や戻り値を把握することはほとんど不可能になる

ライフタイムは、関数が呼び出されるたびに異なる場合がある

これが、ライフタイムに手動でアノテーションを付ける必要がある理由である

ライフタイムの観点から考える: thinking in terms of lifetimes

ライフタイムパラメーターを指定する必要があるかは、 関数が何をしているかによって異なる

例えば、xyのうち常にxをを返すような場合、 yにライフタイムを指定する必要はない

fn longest<'a>(x: &'a str, y: &str) -> &'a str {
    x
}

関数から参照を返す場合、戻り値の型のライフタイムは、 いずれかの引数のライフタイムと一致する必要がある

返された参照が引数のいずれかを参照しない場合、 この関数内で作成された値を参照する必要があるが、 これはダングリング参照になる

fn longest<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str()
}

この場合の最良の解決策は、 参照ではなく、所有データ型を返すことである

構造体定義のライフタイムアノテーション: lifetime annotations in struct definitions

構造体が参照を保持する場合、 構造体の定義内の全ての参照にライフタイムアノテーションを追加する必要がある

struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.')
        .next()
        .expect("Could not find a '.'");
    let i = ImportantExcerpt { part: first_sentence };
}

ジェネリックデータ型と同様に、 構造体の名前の後に山括弧で囲んだジェネリックライフタイムパラメーターの名前を宣言し、 構造体定義の本文でライフタイムパラメーターを使用できるようにしている

このアノテーションは、ImportantExcerptのインスタンスが、 そのpartフィールドに保持している参照より長く存続出来ないことを意味している novelImportantExcerptインスタンスが作成される前に存在し、 ImportantExcerptが範囲外なるまえ、novelも範囲外にならない

ライフタイムエリシオン: lifetime elision

全ての参照にはライフタイムがあるが、 次の関数はコンパイルされている

fn first_word(s: &str) -> &str {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return &s[0..i];
        }
    }

    &s[..]
}

この関数がライフタイムアノテーション無しでコンパイルされる理由は歴史的なものである

Rustの初期バージョンでは、全ての参照に明示的なライフタイムが必要だったため、 このコードはコンパイルされていなかった

fn first_word<'a>(s: &'a str) -> &'a str {

多くのRustコードを書いた後、RustチームはRustプログラマーが特定の状況で何度も同じ ライフタイムアノテーションを挿入していうることを発見した

これらの状況は予測可能であり、いくつかの確定的なパターンが存在した

開発者はこれらのパターンをコンパイラのコードににプログラムしたため、 ボローチェッカーはこれらの状況でのライフタイムを推測でき、 明示的な注釈は必要ない

将来的には、必要なライフタイムアノテーションが更に少なくなる可能性があ

Rustの参照分析にプログラムされたパターンは、ライフタイム除外(エリシオン)ルールと呼ばれる

これらは、プログラマーが従うべき規則ではなく、 コンパイラが考慮する特定のケースのセットであり、 コードがこれらのケースに適合する場合、 ライフタイムを明示的に記述する必要はない

省略(エリシオン)ルールは完全な推論を提供しない

Rustが規則を決定論的に適用しているが、 参照の有効期間が曖昧な場合、 コンパイラは残りの参照の有効期間を推測しない

この場合、コンパイラは推測する代わりにエラーを表示する

エラーは、参照の相互関係を指定するライフタイムアノテーションを追加することで 解決できる

関数のメソッドのパラメーターのライフタイムはインプットライフタイムと呼ばれ、 戻り値のライフタイムはアウトプットライフタイムと呼ばれる

コンパイラは、3つのルールを使用し、 明示的なアノテーションがない場合の有効期間を把握する

最初のルールはインプットライフタイムに適用され、 2,3番目のルールはアウトプットライフタイムに適用される

コンパイラが3つのルールの最後に到達しても、 ライフタイムを把握出来ない参照がまだある場合、コンパイラはエラーで停止する

これらのルールは、 fn定義とimplブロックに適用される

最初のルールは、参照である各パラメーターが独自のライフタイムパラメーターを取得することである

2番目のルールは、 インプットライフタイムパラメーターが1つだけの場合、 そのライフタイムパラメーターは全ての アウトプットライフタイムパラメーターに割り当てられる

3番目のルールは、 複数のインプットライフタイムパラメーターのうち、 1つが&selfまたは&mut selfである場合、 これはメソッドであるため、self内のライフタイムは全て アウトプットライフタイムパラメーターに割り当てられる

この3番目の規則により、必要なシンボルの数がすくなくなるため、 メソッドの読み書きがより便利になる

メソッド定義のライフタイムアノテーション

ライフタイムを持つ構造体にメソッドを実装する場合、 ジェネリック型パラメーターの構文と同じ構文を使用する

ライフタイムパラメーターを宣言して使用する場所は、 それらが構造体フィールドまたはメソッドパラメータと戻り値のどちらに関連するかによって異なる

構造体フィールドのライフタイム名は常にimplキーワードの後に宣言し、 構造体の名前の後に使用する必要がある

implブロック内のメソッドシグネチャでは、 参照は構造体のフィールド内の参照のライフタイムに関連付けられているか、 独立している場合がある

さらに、ライフタイム除外ルールにより、メソッドシグネチャでライフタイムアノテーションが 不要になることがよくある

impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention please: {}", announcement);
        self.part
    }
}

インプットライフタイムは2つあるため、 Rustは最初のライフタイム省略ルールを適用し、 &selfannouncementの両方に独自のライフタイムを与える

また、パラメータの2つが&selfであるため、 戻り値の型は&selfの有効期間を取得し、 全ての有効期間が考慮されている

静的ライフタイム: the static lifetime

議論する必要がある特別なライフタイムの1つは、静的(static)である

この参照は、プログラムの全期間にわたって存続できる

全ての文字列リテラルには、静的ライフタイムがあり、 次のように注釈を付けることができる

let s: &'static str = "I have a static lifetime.";

この文字列のテキストは、 プログラムのバイナリに直接保存され、常に利用可能である

したがって、 全ての文字列リテラルは静的である

ジェネリック型パラメーター、トレイとバインド、ライフタイム: generic type parameters, trait bounds, and lifetimes together

今までのおさらい

use std::fmt::Display;

fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
    where T: Display
{
    println!("Announcement! {}", ann);
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

参考