第6章: ライフタイム入門

学習目標

  • ライフタイムの概念を理解する
  • ライフタイム注釈の書き方を学ぶ
  • ライフタイム省略規則を把握する
  • 関数とライフタイムの関係を理解する

---

6.1 ライフタイムとは何か?

6.1.1 ライフタイムの必要性

ライフタイム(lifetime)は、参照が有効である範囲をコンパイラに伝えるための仕組みです。

fn main() {
    let r;                // ---------+-- 'a
                         //          |
    {                    //          |
        let x = 5;       // -+-- 'b  |
        r = &x;          //  |       |
    }                    // -+       |
                         //          |
    println!("{}", r);   // ---------+
}
// ❌ コンパイルエラー!
// xのライフタイム('b)はrのライフタイム('a)より短い

問題点

  • xは内側のスコープで破棄される
  • rは外側のスコープでxへの参照を保持
  • rは無効なメモリを指す(ダングリング参照)

Rustの解決

error[E0597]: `x` does not live long enough

コンパイラがライフタイムをチェックし、エラーを防ぎます。

6.1.2 ライフタイムの可視化

┌─────────────────────────────────────────────────────────┐
│            ライフタイムの概念図                           │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  fn main() {                                            │
│      let x = 5;        // ────┐ xのライフタイム         │
│      let r = &x;       // ──┐ │ rのライフタイム         │
│                        //   │ │                        │
│      println!("{}", r);// ──┘ │                        │
│  }                     // ────┘                        │
│                                                         │
│  ✅ OK: rのライフタイムはxのライフタイム内に収まっている │
│                                                         │
└─────────────────────────────────────────────────────────┘

---

6.2 関数とライフタイム

6.2.1 問題の例

複数の参照を受け取る関数では、返り値のライフタイムが不明確です:

fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
// ❌ コンパイルエラー!

エラー

error[E0106]: missing lifetime specifier
 --> src/main.rs:1:33
  |
1 | fn longest(x: &str, y: &str) -> &str {
  |               ----     ----     ^ expected named lifetime parameter

問題

  • 返り値はxyのどちらかの参照
  • コンパイラは返り値のライフタイムを判断できない

6.2.2 ライフタイム注釈の追加

ライフタイム注釈(lifetime annotation)で明示します:

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

構文

'a → ライフタイムパラメータ(アポストロフィ + 名前)
<'a> → ジェネリックライフタイムの宣言
&'a str → ライフタイム'aを持つ参照

意味

「返り値の参照は、xとyのうち短い方のライフタイムと同じ」

6.2.3 ライフタイムの動作

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

    {
        let string2 = String::from("xyz");
        let result = longest(string1.as_str(), string2.as_str());
        println!("長い方: {}", result);
    } // ✅ OK: resultはこのスコープ内でのみ使用
}

分析

  • string1のライフタイム:main全体
  • string2のライフタイム:内側のスコープ
  • resultのライフタイム:短い方(string2と同じ)
  • resultを内側のスコープで使用しているのでOK

エラーの例

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

    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    } // string2が破棄される

    println!("長い方: {}", result); // ❌ エラー!
}

エラー

error[E0597]: `string2` does not live long enough

---

6.3 ライフタイム注釈の文法

6.3.1 基本的な構文

&i32        // 参照
&'a i32     // 明示的なライフタイム付き参照
&'a mut i32 // 明示的なライフタイム付き可変参照

6.3.2 複数のライフタイム

異なるライフタイムを持つ参照:

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

意味

  • xはライフタイム'a
  • yはライフタイム'b
  • 返り値は'a(xと同じライフタイム)

使用例

fn main() {
    let string1 = String::from("hello");

    {
        let string2 = String::from("world");
        let result = example(&string1, &string2);
        println!("{}", result);
    } // string2は破棄されるが、resultはstring1を参照しているのでOK
}

6.3.3 ライフタイムの制約

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

意味

'b: 'a → 'bは'a以上に長く生きる

使用例

fn main() {
    let string1 = String::from("short");
    let string2 = String::from("longer");

    // string2のライフタイムがstring1以上であることを保証
    let result = example(&string1, &string2);
}

---

6.4 構造体とライフタイム

6.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().expect("Could not find a '.'");

    let i = ImportantExcerpt {
        part: first_sentence,
    };

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

ルール

構造体のインスタンスは、参照しているデータより長く生きられない

エラーの例

fn main() {
    let i;

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

        i = ImportantExcerpt {
            part: first_sentence,
        };
    } // novelが破棄される

    // println!("{}", i.part); // ❌ エラー!
}

6.4.2 複数の参照を持つ構造体

struct Context<'a, 'b> {
    prefix: &'a str,
    suffix: &'b str,
}

fn main() {
    let prefix = String::from("Hello");
    let suffix = String::from("World");

    let context = Context {
        prefix: &prefix,
        suffix: &suffix,
    };

    println!("{}, {}", context.prefix, context.suffix);
}

---

6.5 ライフタイム省略規則

6.5.1 省略規則の概要

多くの場合、ライフタイム注釈は省略できます。コンパイラが以下のライフタイム省略規則(lifetime elision rules)に従って推論します:

規則1:入力ライフタイム

各参照パラメータは独自のライフタイムを持つ

// 書いたコード
fn first_word(s: &str) -> &str

// コンパイラが推論
fn first_word<'a>(s: &'a str) -> &str

規則2:単一の入力ライフタイム

入力ライフタイムが1つだけなら、それが出力に適用される

// 書いたコード
fn first_word(s: &str) -> &str

// コンパイラが推論
fn first_word<'a>(s: &'a str) -> &'a str

規則3:&selfまたは&mut self

メソッドで、&selfがあれば、そのライフタイムが出力に適用される

// 書いたコード
impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

// 明示的に書くと
impl<'a> ImportantExcerpt<'a> {
    fn level(&self) -> i32 {
        3
    }
}

6.5.2 省略できる例

// ✅ 省略可能
fn first_word(s: &str) -> &str {
    &s[..1]
}

// ✅ 省略可能(規則2)
fn parse(text: &str) -> &str {
    &text[..]
}

// ✅ 省略可能(規則3)
impl<'a> ImportantExcerpt<'a> {
    fn announce_and_return_part(&self, announcement: &str) -> &str {
        println!("Attention: {}", announcement);
        self.part
    }
}

6.5.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 }
}

---

6.6 'staticライフタイム

6.6.1 'staticとは

'static:プログラム全体の期間有効なライフタイム

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

文字列リテラル

  • プログラムのバイナリに直接埋め込まれる
  • プログラムの実行中、常に有効

6.6.2 'staticの使用例

// 文字列リテラル
const MESSAGE: &'static str = "Hello, world!";

// 静的変数
static LANGUAGE: &'static str = "Rust";

fn main() {
    println!("{}", MESSAGE);
    println!("{}", LANGUAGE);
}

6.6.3 'staticの注意点

誤解

「'staticを付けると全てのライフタイム問題が解決する」

現実

'staticは本当に必要な場合のみ使用すべき

悪い例

fn dangle() -> &'static str {
    let s = String::from("hello");
    // &s // ❌ これでは解決しない
    "hello" // 文字列リテラルを返す
}

---

6.7 実践例

例1:文字列スライスの比較

fn longest_word<'a>(text: &'a str) -> &'a str {
    text.split_whitespace()
        .max_by_key(|word| word.len())
        .unwrap_or("")
}

fn main() {
    let sentence = String::from("The quick brown fox");
    let longest = longest_word(&sentence);
    println!("最長の単語: {}", longest);
}

例2:構造体メソッド

struct Excerpt<'a> {
    text: &'a str,
}

impl<'a> Excerpt<'a> {
    fn new(text: &'a str) -> Self {
        Excerpt { text }
    }

    fn get_text(&self) -> &str {
        self.text
    }

    fn first_char(&self) -> Option<char> {
        self.text.chars().next()
    }
}

fn main() {
    let text = String::from("Hello, Rust!");
    let excerpt = Excerpt::new(&text);

    println!("テキスト: {}", excerpt.get_text());
    if let Some(ch) = excerpt.first_char() {
        println!("最初の文字: {}", ch);
    }
}

例3:複数の参照を返す

fn split_at_first_space<'a>(text: &'a str) -> (&'a str, &'a str) {
    match text.find(' ') {
        Some(pos) => (&text[..pos], &text[pos + 1..]),
        None => (text, ""),
    }
}

fn main() {
    let sentence = String::from("Hello World");
    let (first, rest) = split_at_first_space(&sentence);

    println!("最初: {}", first);
    println!("残り: {}", rest);
}

---

6.8 ライフタイムのベストプラクティス

6.8.1 設計の原則

┌─────────────────────────────────────────────────────────┐
│        ライフタイム設計のガイドライン                     │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 所有権を優先                                        │
│     → 可能なら参照ではなく所有権を使う                  │
│                                                         │
│  2. ライフタイムは最小限に                              │
│     → 省略規則に任せる                                  │
│                                                         │
│  3. 構造体の参照は避ける                                │
│     → データを所有する方が簡単                          │
│                                                         │
│  4. 'staticは慎重に                                    │
│     → 本当に必要な場合のみ                             │
│                                                         │
└─────────────────────────────────────────────────────────┘

6.8.2 所有権 vs 参照

// ✅ 良い:データを所有
struct Config {
    name: String,
    value: String,
}

// ❌ 複雑:ライフタイム管理が必要
struct ConfigRef<'a> {
    name: &'a str,
    value: &'a str,
}

使い分け

  • 所有権:データの永続的な保持
  • 参照:一時的な借用、パフォーマンス最適化
  • ---

    6.9 まとめ

    学んだこと

  • ライフタイムの概念
- 参照の有効期間 - ダングリング参照の防止

  • ライフタイム注釈
- 'a'bの構文 - 関数、構造体での使用

  • ライフタイム省略規則
- 多くの場合、明示不要 - コンパイラが推論

  • 'static
- プログラム全体で有効 - 文字列リテラルのライフタイム

次のステップ

次の章では、型システムを学びます: