12. ライフタイム

🎯 この章のゴール

  • ライフタイムが 何を防いでいるのか を理解する
  • 関数・構造体にライフタイム注釈を付けられるようになる
  • 省略規則を知り、書かなくていい場面を見分ける

💡 ひとことで言うと

ライフタイムは 参照の賞味期限ラベル。「この戻り値は、引数たちの賞味期限のうち短い方まで有効です」とコンパイラに伝えます。

何を防いでいる?

fn main() {
    let r;
    {
        let x = 5;
        r = &x;      // error[E0597]: `x` does not live long enough
    }                // x はここで消える
    println!("{r}"); // 消えた x を指す参照(ダングリング)を使おうとしている
}
Playground で開く ↗

借用チェッカー は「参照先が参照より長生きしているか」をコンパイル時に確認します。

関数のライフタイム注釈

2 つの参照のどちらかを返す関数は、戻り値がどちらの寿命に従うのかコンパイラには分かりません。

fn longest(x: &str, y: &str) -> &str { // error[E0106]: missing lifetime specifier
    if x.len() > y.len() { x } else { y }
}

fn main() {
    println!("{}", longest("ab", "c"));
}
Playground で開く ↗

'a で関係を明示します。

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

fn main() {
    let a = String::from("abcd");
    let b = "xyz";
    println!("{}", longest(a.as_str(), b)); // abcd
}
Playground で開く ↗

読み方: 「x と y が両方とも有効な期間 'a の間、戻り値も有効」。 つまり戻り値は 短い方 の寿命に縛られます。

構造体のライフタイム

構造体に参照を持たせるなら、注釈が必要です。

struct Excerpt<'a> {
    part: &'a str, // Excerpt は part の参照先より長生きできない
}

impl<'a> Excerpt<'a> {
    fn part(&self) -> &str {
        self.part
    }
}

fn main() {
    let novel = String::from("昔々あるところに。おじいさんとおばあさんが…");
    let first = novel.split('。').next().unwrap();
    let e = Excerpt { part: first };
    println!("{}", e.part());
}
Playground で開く ↗

省略規則: 書かなくていい場面

毎回書くのは大変なので、次の規則で推論できる場合は省略できます。

  1. 引数の各参照に、それぞれ別のライフタイムが付く
  2. 入力の参照が 1 つだけ なら、出力はそのライフタイム
  3. メソッドで &self / &mut self があれば、出力は self のライフタイム

ほとんどのケースは規則 2・3 でカバーされるので、実際に注釈を書くのは「参照を 2 つ以上受け取り、参照を返す関数」や「参照を持つ構造体」くらいです。

ライフタイムで詰まったら、参照ではなく所有する値(String など)を返す/持つ のも立派な解決策です。最初から完璧に借用しようとしなくて大丈夫。

‘static

fn main() {
    let s: &'static str = "プログラム終了まで有効";
    println!("{s}");
}
Playground で開く ↗

🧪 やってみよう

下の書きかけコードの TODO を埋めて、▶ 実行 で確かめよう。todo!() のままだと実行時に panic します。

2 つの文字列スライスのうち、辞書順で小さい方を返す関数を書いてみよう。

fn smaller<'a>(a: &'a str, b: &'a str) -> &'a str {
    todo!("ヒント: &str は < で比較できる")
}

fn main() {
    println!("{}", smaller("banana", "apple")); // apple
}
Playground で開く ↗
💡 解答例を見る
fn smaller<'a>(a: &'a str, b: &'a str) -> &'a str {
    if a < b { a } else { b }
}

fn main() {
    println!("{}", smaller("banana", "apple")); // apple
}
Playground で開く ↗

📝 まとめ

  • ライフタイムはダングリング参照を防ぐための仕組み
  • 注釈は寿命を変えず、参照同士の 関係 を表す
  • 省略規則のおかげで、書く必要がある場面は意外と少ない

✅ 理解度クイズ

Q1
ライフタイム注釈 'a の役割として正しいものは?
Q2
次のコードはコンパイルできる?
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

fn main() {
    let a = String::from("long string");
    let result;
    {
        let b = String::from("xyz");
        result = longest(a.as_str(), b.as_str());
    }
    println!("{result}");
}
Q3
次の関数はライフタイム注釈なしでコンパイルできる?
fn first_word(s: &str) -> &str {
    s.split(' ').next().unwrap_or("")
}
Q4
&'static str の 'static が意味するのは?