Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ライフタイム入門

参照が有効な期間を示す「ライフタイム」について学びます。

ライフタイムとは

ライフタイムは「参照が有効な期間」を表します。

fn main() {
    let r;                // ---------+-- 'a
                          //          |
    {                     //          |
        let x = 5;        // -+-- 'b  |
        r = &x;           //  |       |
    }                     // -+       |  (xが破棄される)
                          //          |
    // println!("{}", r); // エラー! |  (rは無効なメモリを指す)
}                         // ---------+

xのライフタイム'brのライフタイム'aより短いため、エラーになります。

なぜライフタイムが必要なのか

ダングリング参照を防ぐ

#![allow(unused)]
fn main() {
// これはコンパイルエラー
fn dangle() -> &String {
    let s = String::from("hello");
    &s  // sへの参照を返そうとしている
}  // ← sがドロップされる!参照先がなくなる
}

ライフタイムによって、コンパイラは「参照が参照先より長生きしないか」をチェックします。

関数のライフタイム注釈

問題のあるコード

#![allow(unused)]
fn main() {
// これはコンパイルエラー
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
}

エラー:

error[E0106]: missing lifetime specifier

コンパイラは「返される参照がxのライフタイムなのかyのライフタイムなのかわからない」と言っています。

ライフタイム注釈で解決

#![allow(unused)]
fn main() {
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
}
  • <'a>: ライフタイムパラメータの宣言
  • &'a str: ライフタイム'aを持つ文字列参照

この注釈は「返される参照は、xとyの両方が有効な間だけ有効」という意味です。

ライフタイム注釈の構文

#![allow(unused)]
fn main() {
&i32        // 参照
&'a i32     // ライフタイム'aを持つ参照
&'a mut i32 // ライフタイム'aを持つ可変参照
}

具体例で理解する

fn main() {
    let string1 = String::from("long string is long");

    {
        let string2 = String::from("xyz");
        let result = longest(&string1, &string2);
        println!("最長: {}", result);  // OK: string2はまだ有効
    }

    // ここでresultを使おうとすると...?
}

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

これはOKです。resultは内側のスコープ内でのみ使われており、その時点ではstring1string2も有効だからです。

エラーになるケース

fn main() {
    let string1 = String::from("long string is long");
    let result;

    {
        let string2 = String::from("xyz");
        result = longest(&string1, &string2);
    }  // string2がドロップされる

    println!("最長: {}", result);  // エラー!string2はもうない
}

resultstring2を参照する可能性があるのに、string2はスコープを抜けて無効になっています。

ライフタイム省略規則

すべてのケースでライフタイムを書く必要はありません。

省略できるケース

#![allow(unused)]
fn main() {
// 明示的なライフタイム
fn first_word<'a>(s: &'a str) -> &'a str { ... }

// 省略可能(コンパイラが推論)
fn first_word(s: &str) -> &str { ... }
}

省略規則(参考)

  1. 各引数の参照は別々のライフタイムを得る
  2. 入力ライフタイムが1つなら、それが出力に適用される
  3. &self&mut selfがある場合、selfのライフタイムが出力に適用される

最初は「省略できることがある」程度の理解で大丈夫です。

構造体のライフタイム

参照を持つ構造体にはライフタイム注釈が必要です。

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

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().unwrap();

    let excerpt = ImportantExcerpt {
        part: first_sentence,
    };

    println!("{}", excerpt.part);
}

この構造体は「partが参照しているデータが有効な間だけ有効」です。

’static ライフタイム

'staticは「プログラム全体で有効」な特別なライフタイムです。

#![allow(unused)]
fn main() {
let s: &'static str = "Hello, world!";
}

文字列リテラルは'staticライフタイムを持ちます。プログラムに埋め込まれているため、常に有効です。

注意

'staticを安易に使うのは避けましょう。本当に必要なケースは稀です。

ライフタイムのメンタルモデル

┌────────────────────────────────────────────────────────┐
│    ライフタイム = 「参照が有効な期間の制約」              │
├────────────────────────────────────────────────────────┤
│                                                        │
│  目的: ダングリング参照を防ぐ                           │
│                                                        │
│  ルール:                                               │
│  ・参照は参照先より長生きしてはいけない                  │
│  ・関数が複数の参照を受け取り参照を返す場合、            │
│    ライフタイム注釈で関係を明示                         │
│                                                        │
│  省略: 多くの場合、コンパイラが推論してくれる            │
│                                                        │
└────────────────────────────────────────────────────────┘

まとめ

概念説明
ライフタイム参照が有効な期間
'aライフタイムパラメータ
注釈の目的参照間の関係を明示
'staticプログラム全体で有効

最初は難しく感じますが、使っていくうちに自然と理解できるようになります。


確認テスト

Q1. ライフタイムの主な目的は何ですか?

Q2. 'staticライフタイムが意味するものは何ですか?

Q3. 以下のコードはコンパイルできますか?
let r; { let x = 5; r = &x; } println!("{}", r);

Q4. 関数fn longest(x: &str, y: &str) -> &strがコンパイルエラーになる理由は何ですか?

Q5. 構造体に参照を持たせる場合、必要なものは何ですか?


次のドキュメント: 05_slices.md