Chapter 3: ライフタイム基礎

この章の目標

この章を読み終えると、以下のことが理解できるようになります:

  • ライフタイムとは何か、なぜ必要なのか
  • ライフタイム注釈の基本的な書き方
  • ライフタイムエリジョンの3つのルール
  • ライフタイムのサブタイプ関係
  • 構造体とライフタイム
  • よくあるライフタイムエラーとその修正方法
  • ライフタイムとは

    1.1 ライフタイムの定義

    ライフタイムは、参照が有効である期間を表す型レベルの情報です。

    fn main() {
        let s = String::from("hello");
        //  └── 's のライフタイム開始
        let r = &s;
        //  └── 'r のライフタイム開始('s の部分集合)
        println!("{}", r);
        //  └── 'r のライフタイム終了
    }
    //  └── 's のライフタイム終了
    

    重要な点

  • ライフタイムは参照に付く(値そのものではない)
  • ライフタイムはコンパイル時の概念(実行時には存在しない)
  • ライフタイムは関係性を表す(絶対的な時間ではない)
  • 1.2 なぜライフタイムが必要なのか

    ライフタイムがないと、ダングリングポインタを防げません:

    // ライフタイムチェックなしだとしたら...
    fn dangle() -> &String {
        let s = String::from("hello");
        &s  // sはここで破棄される → ダングリングポインタ!
    }
    

    Rustはライフタイムチェックで、このようなコードを拒否します:

    error[E0515]: cannot return reference to local variable `s`
     --> src/main.rs:3:5
      |
    3 |     &s
      |     ^^ returns a reference to data owned by the current function
    

    2. ライフタイム注釈の基礎

    2.1 基本的な構文

    ライフタイム注釈は、' で始まる識別子です:

    &'a T      // 不変参照、ライフタイム 'a
    &'a mut T  // 可変参照、ライフタイム 'a
    

    命名規則

  • 'a, 'b, 'c が一般的
  • 'static は特別なライフタイム(プログラム全体)
  • 意味のある名前も付けられる: 'input, 'output
  • 2.2 関数のライフタイム注釈

    例1: 最も長い文字列を返す

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

    解説

    <'a>: ライフタイムパラメータの宣言
    x: &'a str: xのライフタイムは'a
    y: &'a str: yのライフタイムは'a
    -> &'a str: 戻り値のライフタイムは'a
    
    意味: 戻り値のライフタイムは、xとyの短い方
    

    使用例

    fn main() {
        let s1 = String::from("long string");
        let s2 = String::from("short");
        let result = longest(&s1, &s2);
        println!("Longest: {}", result);
    }
    

    例2: ライフタイムが異なる場合

    fn main() {
        let s1 = String::from("long string");
        let result;
        {
            let s2 = String::from("short");
            result = longest(&s1, &s2);
            // エラー!resultのライフタイムがs2より長い
        }
        println!("Longest: {}", result);
    }
    

    エラーメッセージ

    error[E0597]: `s2` does not live long enough
     --> src/main.rs:6:30
      |
    6 |         result = longest(&s1, &s2);
      |                              ^^^^ borrowed value does not live long enough
    7 |     }
      |     - `s2` dropped here while still borrowed
    8 |     println!("Longest: {}", result);
      |                              ------ borrow later used here
    

    2.3 複数のライフタイムパラメータ

    異なるライフタイムを持つ参照を扱う場合:

    fn first_word<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
        x.split_whitespace().next().unwrap()
    }
    

    解説

    'a と 'b は独立したライフタイム
    戻り値は 'a(xのライフタイム)
    yのライフタイム 'b は戻り値に影響しない
    

    2.4 ライフタイムの制約

    ライフタイム間に制約を付けることができます:

    fn announce_and_return<'a, 'b>(x: &'a str, y: &'b str) -> &'a str
    where
        'b: 'a,  // 'b は 'a より長い(またはイコール)
    {
        println!("Announcement: {}", y);
        x
    }
    

    記号の意味

    'b: 'a  ←  'b は 'a よりアウトライブ(長生き)する
             = 'b ⊇ 'a
             = 'b のライフタイムは 'a 以上
    

    3. ライフタイムエリジョン

    3.1 エリジョンとは

    ライフタイムエリジョンは、明示的なライフタイム注釈を省略できるルールです。

    // エリジョンあり(書かなくて良い)
    fn first_word(s: &str) -> &str {
        s.split_whitespace().next().unwrap()
    }
    
    // エリジョンなし(明示的)
    fn first_word<'a>(s: &'a str) -> &'a str {
        s.split_whitespace().next().unwrap()
    }
    
    // 両方同じ意味
    

    3.2 エリジョンの3つのルール

    コンパイラは、以下の3つのルールでライフタイムを推論します:

    ルール1: 各入力パラメータに異なるライフタイムを割り当てる

    fn foo(x: &i32)
    // ↓ コンパイラが推論
    fn foo<'a>(x: &'a i32)
    
    fn bar(x: &i32, y: &i32)
    // ↓ コンパイラが推論
    fn bar<'a, 'b>(x: &'a i32, y: &'b i32)
    

    ルール2: 入力ライフタイムが1つなら、出力に適用

    fn foo(x: &i32) -> &i32
    // ↓ コンパイラが推論
    fn foo<'a>(x: &'a i32) -> &'a i32
    

    ルール3: 入力に &self または &mut self があれば、そのライフタイムを出力に適用

    impl Foo {
        fn bar(&self, x: &i32) -> &i32
        // ↓ コンパイラが推論
        fn bar<'a, 'b>(&'a self, x: &'b i32) -> &'a i32
    }
    

    3.3 エリジョンが適用されない例

    // エラー!ルールでは推論できない
    fn longest(x: &str, y: &str) -> &str {
        if x.len() > y.len() { x } else { y }
    }
    

    なぜエラー?

    ルール1適用: fn longest<'a, 'b>(x: &'a str, y: &'b str) -> &str
    ルール2適用: 入力が2つ → 適用できない
    ルール3適用: selfなし → 適用できない
    結論: 出力のライフタイムが決まらない → エラー
    

    修正方法

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

    4. 構造体とライフタイム

    4.1 基本的な構造体

    構造体が参照を持つ場合、ライフタイム注釈が必要です:

    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,
        };
    }
    

    解説

    <'a>: 構造体のライフタイムパラメータ
    part: &'a str: partのライフタイムは'a
    
    意味: ImportantExcerptのインスタンスは、
          partが指すデータより長生きできない
    

    4.2 複数のライフタイム

    struct Context<'a, 'b> {
        first: &'a str,
        second: &'b str,
    }
    
    fn main() {
        let first = String::from("first");
        {
            let second = String::from("second");
            let ctx = Context {
                first: &first,
                second: &second,
            };
            // ctxはここでドロップされる
        }
    }
    

    4.3 構造体のメソッドとライフタイム

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

    エリジョン適用

    // エリジョン前(明示的)
    impl<'a> ImportantExcerpt<'a> {
        fn announce_and_return_part<'b>(
            &'a self,
            announcement: &'b str,
        ) -> &'a str {
            println!("Attention please: {}", announcement);
            self.part
        }
    }
    
    // エリジョン後(省略形)
    impl<'a> ImportantExcerpt<'a> {
        fn announce_and_return_part(&self, announcement: &str) -> &str {
            println!("Attention please: {}", announcement);
            self.part
        }
    }
    

    5. ライフタイムのサブタイプ関係

    5.1 サブタイプとは

    ライフタイムにもサブタイプ関係があります:

    'a: 'b  ←  'a は 'b のサブタイプ
             = 'a は 'b より長い(またはイコール)
             = 'a ⊇ 'b
    

    直感的な理解

    長いライフタイムは、短いライフタイムの「サブタイプ」
    ↓
    長いライフタイムの参照は、短いライフタイムが期待される場所で使える
    

    5.2 共変性(Covariance)

    fn foo<'a>(x: &'a str) -> &'a str {
        x
    }
    
    fn main() {
        let long_lived = String::from("long");
        {
            let short_lived = String::from("short");
            let result = foo(&long_lived);  // OK
            // 'long > 'short だが、foo は 'short でも動く
        }
    }
    

    5.3 反変性(Contravariance)

    関数ポインタの引数は反変です:

    fn apply<'a>(f: fn(&'a str), s: &'a str) {
        f(s)
    }
    
    fn print_short(s: &str) {
        println!("{}", s);
    }
    
    fn main() {
        let s = String::from("hello");
        apply(print_short, &s);  // OK
    }
    

    6. 'static ライフタイム

    6.1 'static とは

    'static は、プログラム全体のライフタイムです:

    let s: &'static str = "hello";
    

    特徴

  • 文字列リテラルはすべて 'static
  • バイナリに埋め込まれる
  • プログラム終了まで有効
  • 6.2 'static の使用例

    例1: 定数

    const HELLO: &'static str = "Hello, world!";
    
    fn main() {
        println!("{}", HELLO);
    }
    

    例2: スレッド間の共有

    use std::thread;
    
    static GLOBAL: &str = "global";
    
    fn main() {
        thread::spawn(|| {
            println!("{}", GLOBAL);  // OK
        });
    }
    

    6.3 'static の誤解

    誤解: すべての参照を 'static にすれば良い

    真実: 'static は非常に制限的

    // これはエラー
    fn dangle() -> &'static String {
        let s = String::from("hello");
        &s  // エラー!sは'staticではない
    }
    

    7. よくあるライフタイムエラー

    7.1 戻り値の参照が短命

    // エラー
    fn dangle() -> &String {
        let s = String::from("hello");
        &s
    }
    
    // 修正1: 所有権を返す
    fn fixed1() -> String {
        let s = String::from("hello");
        s
    }
    
    // 修正2: 'static を使う
    fn fixed2() -> &'static str {
        "hello"
    }
    

    7.2 構造体のライフタイム忘れ

    // エラー
    struct Excerpt {
        part: &str,
    }
    
    // 修正
    struct Excerpt<'a> {
        part: &'a str,
    }
    

    7.3 ライフタイムの推論失敗

    // エラー
    fn longest(x: &str, y: &str) -> &str {
        if x.len() > y.len() { x } else { y }
    }
    
    // 修正
    fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
        if x.len() > y.len() { x } else { y }
    }
    

    8. 実践的なパターン

    8.1 オプショナルライフタイム

    struct Config<'a> {
        query: &'a str,
        filename: &'a str,
    }
    
    impl<'a> Config<'a> {
        fn new(args: &'a [String]) -> Result<Config<'a>, &'static str> {
            if args.len() < 3 {
                return Err("not enough arguments");
            }
    
            let query = &args[1];
            let filename = &args[2];
    
            Ok(Config { query, filename })
        }
    }
    

    8.2 イテレータとライフタイム

    struct Lines<'a> {
        text: &'a str,
    }
    
    impl<'a> Iterator for Lines<'a> {
        type Item = &'a str;
    
        fn next(&mut self) -> Option<Self::Item> {
            if self.text.is_empty() {
                return None;
            }
    
            if let Some(pos) = self.text.find('\n') {
                let line = &self.text[..pos];
                self.text = &self.text[pos + 1..];
                Some(line)
            } else {
                let line = self.text;
                self.text = "";
                Some(line)
            }
        }
    }
    

    まとめ

    重要なポイント

  • ライフタイムは参照が有効な期間を表す
  • ライフタイムエリジョンで多くの場合は省略可能
  • 構造体に参照があればライフタイム注釈が必要
  • 'static は特別なライフタイム(プログラム全体)
  • エラーメッセージを読んで理解する
  • 実践的なアドバイス

  • まずはエリジョンに任せる
  • エラーが出たらライフタイムを明示
  • 所有権を渡すことも検討
  • 'static は慎重に使う

次の章へ

次の章では、ライフタイム応用として、より高度なパターン(高階ライフタイム、ファントムライフタイム、ライフタイムのサブタイピング)を学びます。