このページの目次

ライフタイム

ライフタイム('a)は実行時の値ではなく、参照の有効スコープに対するコンパイル時の制約です。elisionルール(コンパイラによる自動推論)から明示的な注釈(struct/分散性/HRTB)に至るまで、ライフタイム注釈は本質的に借用チェッカーに対して「これらの参照のうち、誰が誰よりも長く生存するか」を伝えています。

ライフタイムはコンパイル時の概念

ライフタイムパラメータ('a)は実行時の値ではなく、コンパイル時の制約です:この参照の有効スコープは 'a より長くすることはできません。コンパイラはMIR上でリージョン推論を用いて最小のライフタイムを推論します。

ライフタイムのelision

ほとんどの関数では明示的なライフタイム注釈は不要です。コンパイラは以下の3つのルールに基づいて自動的に推論します:

  1. ライフタイムが注釈されていない参照パラメータには、それぞれ独立したライフタイムが割り当てられます(fn foo<'a, 'b>(x: &'a T, y: &'b U)
  2. 入力ライフタイムが1つのみの場合、そのライフタイムがすべての出力ライフタイムに割り当てられます(fn foo<'a>(x: &'a T) -> &'a U
  3. 複数の入力ライフタイムの中に &self または &mut self が含まれている場合、self のライフタイムがすべての出力に割り当てられます

ルール2が最も一般的です:単一の参照パラメータ → 返される参照はそのパラメータを借用しているとみなされます。ルール3はメソッドチェーンが動作する理由です:self の借用が返り値に渡されます。

Struct におけるライフタイム

struct が参照を含む場合、ライフタイムを明示的に注釈する必要があります:

struct Excerpt<'a> {
    part: &'a str,                   // この参照は 'a より長く生存できません
}

impl<'a> Excerpt<'a> {
    fn announce(&self, msg: &str) -> &str {  // ルール3: 出力は self を借用
        println!("{}", msg);
        self.part
    }
}

コンパイラの保証:Excerpt<'a> のインスタンスは、参照されている &'a str より長く生存することはできません。

'static

'static は唯一「特別な」意味を持つライフタイムです。これは参照されるデータがプログラムの全期間にわたって生存することを示します。発生箇所:

  • 文字列リテラル:"hello" の型は &'static str です。コンパイル時にバイナリに埋め込まれ、常に有効です。
  • Box::leak(Box::new(T)) により &'static mut T を生成できます。
  • static 変数および const 参照

分散性(Variance)

Rust のライフタイムには subtyping variance があります。理解すべき重要なルール:

  • 'long: 'short(つまり 'long'short より長く生存するため、'long'short の制約を満たす)
  • &'a T: 'a に対して共変(covariant)です('long: 'short ならば、&'long T&'short T として使用可能)
  • &'a mut T: 'a に対して不変(invariant)です(ミュータブル参照のライフタイムを短縮することはできません。そうすると aliasing XOR mutation の制約に違反する可能性があります)
fn assign<T>(to: &mut T, from: T) { *to = from; }

let mut x: &'static str = "hello";
{
    let y = String::from("world");
    // assign(&mut x, y.as_str());   // エラー: T = &'static str (to による), しかし y は &'y str
}
// これがコンパイルできると、x は y のデータを指すことになり、y が drop された後にダングリングポインタになります

HRTB(Higher-Ranked Trait Bounds)

クロージャのパラメータにライフタイムがあるが、明示的に宣言する必要がない場合:

fn apply<F>(f: F) where F: for<'a> Fn(&'a str) -> &'a str { ... }
// 「f は**任意の**ライフタイム 'a に対して Fn(&'a str) -> &'a str を満たす」
// caller 側で 'a を指定する必要はありません。コンパイラが自動的に量化します。

PhantomData

struct にライフタイムはあるが、参照を直接保持していない場合、PhantomData を使用して所有権/借用関係をマークします:

use std::marker::PhantomData;

struct MyIter<'a, T> {
    ptr: *const T,                   // 裸のポインタ — Rust の借用関係を持たない!
    _marker: PhantomData<&'a T>,     // コンパイラに伝える:「この struct は論理的に &'a T を保持している」
}

参考

  • Rust Book: 第10章 3節 — ライフタイムによる参照の検証
  • Rust Reference: ライフタイム elision、分散性
  • RFC: RFC 2094 (NLL)、RFC 387 (Higher-Ranked Trait Bounds)

キーワード: lifetime, elision, 'static, variance, covariant, invariant, HRTB, PhantomData, region inference