Chapter 1: ライフタイムの形式的理解

学習目標

  • ライフタイムの数学的定義を理解する
  • 領域ベース型システムの理論を学ぶ
  • サブタイピング関係を深く理解する
  • variance(変性)の概念を習得する
  • ライフタイム推論アルゴリズムを学ぶ

---

1.1 ライフタイムの数学的定義

1.1.1 ライフタイムとは何か

形式的定義

ライフタイム α は、プログラムの実行における
時間区間の集合である。

α = {[start₁, end₁), [start₂, end₂), ...}

where startᵢ, endᵢ ∈ プログラムポイント

fn example() {
    let x = 5;      // ← P1
    let r = &x;     // ← P2
    println!("{}", r); // ← P3
}                   // ← P4

// 'x のライフタイム: [P1, P4)
// 'r のライフタイム: [P2, P4)

1.1.2 ライフタイムの包含関係

定義

'a outlives 'b  ⟺  'a ⊇ 'b
記法: 'a: 'b

つまり、'aが有効な期間は'bが有効な期間を完全に包含する

fn foo<'a, 'b>(x: &'a i32, y: &'b i32) -> &'a i32
where
    'a: 'b,  // 'a outlives 'b
{
    x
}

視覚化

時間軸 ──────────────────────────►

'a:      ┌──────────────────────────┐
         │                          │
'b:      │    ┌──────────┐          │
         │    │          │          │
         └────┴──────────┴──────────┘

'a: 'b が成立('a は 'b を包含)

---

1.2 領域ベース型システム

1.2.1 Tofte-Talpin 領域推論

歴史的背景

  • 1994年にMads TofteとJean-Pierre Talpinが提案
  • 関数型言語のメモリ管理のため
  • Rustのライフタイムシステムの理論的基礎

基本概念

型 τ ::= int | bool | τ₁ → τ₂ | ref^ρ τ
               領域注釈 ──┘

ρ: 領域(region)= Rustのライフタイム

1.2.2 型付け規則

参照の生成

Γ ⊢ e : τ    ρ fresh
─────────────────────
Γ ⊢ ref e : ref^ρ τ

参照の読み取り

Γ ⊢ e : ref^ρ τ
────────────────
Γ ⊢ !e : τ

---

1.3 サブタイピングと変性

1.3.1 ライフタイムのサブタイピング

定義

'a: 'b
─────────────────────
&'a T <: &'b T

(より長いライフタイムの参照は、
  より短いライフタイムの参照として使える)

fn take_short<'a>(r: &'a i32) {
    // ...
}

fn example() {
    let x = 5;           // 'x
    {
        let y = 10;      // 'y (shorter)
        let r: &i32 = &x;  // 型: &'x i32

        // &'x i32 <: &'y i32 なので OK
        take_short::<'y>(r);
    }
}

1.3.2 Variance(変性)

定義

┌─────────────────────────────────────────────────────────┐
│          Variance の3種類                                │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Covariant(共変):                                      │
│  'a: 'b  ⟹  F<'a> <: F<'b>                            │
│  例: &'a T, *const T                                    │
│                                                         │
│  Contravariant(反変):                                  │
│  'a: 'b  ⟹  F<'b> <: F<'a>                            │
│  例: fn(&'a T)                                          │
│                                                         │
│  Invariant(不変):                                      │
│  サブタイピング関係なし                                  │
│  例: &'a mut T, Cell<'a T>                              │
│                                                         │
└─────────────────────────────────────────────────────────┘

具体例

// Covariant: &'a T
fn covariant_example<'a, 'b>(x: &'a i32) -> &'b i32
where
    'a: 'b,
{
    x  // OK: &'a i32 <: &'b i32
}

// Contravariant: for<'a> fn(&'a T)
fn contravariant_example<'a, 'b>()
where
    'a: 'b,
{
    let f: fn(&'a i32) = |_| {};
    let g: fn(&'b i32) = f;  // OK: 'a: 'b なら fn(&'b) <: fn(&'a)
}

// Invariant: &'a mut T
fn invariant_example<'a, 'b>(x: &'a mut i32) -> &'b mut i32
where
    'a: 'b,
{
    // x  // エラー!&'a mut i32 は &'b mut i32 のサブタイプではない
    unimplemented!()
}

1.3.3 なぜ &mut T は Invariant か

// もし &'a mut T が covariant だったら...
fn evil<'a, 'b>(short: &'a mut &'b i32, long: &'a mut &'a i32) {
    // &'a mut &'a i32 <: &'a mut &'b i32 と仮定
    *short = *long;  // 型は合うように見える
    // しかし 'b < 'a なので、shortが長いライフタイムを持つことに!
}

// 実際にはコンパイルエラー

---

1.4 ライフタイム推論

1.4.1 制約ベース推論

アルゴリズムの概要

1. 各参照にライフタイム変数を割り当て
   let x = &y  ⟹  x: &'a T

2. 制約を生成
   - 代入: x = y  ⟹  'x ⊇ 'y
   - 関数呼び出し: f(x)  ⟹  制約を関数シグネチャから導出
   - スコープ: { let x = ...; }  ⟹  'x ⊆ スコープ

3. 制約を解く(最小不動点)
   - 最も制限的な解を求める

4. 矛盾があればエラー

fn example() {
    let x = 5;        // 'x
    let r = &x;       // 'r
    let s = r;        // 's
    println!("{}", s);
}

// 制約:
// 'r ⊆ 'x  (r は x を借用)
// 's = 'r  (s は r のコピー)
// 's ⊆ println呼び出しまで

// 解:
// 'x = [L1, L4)
// 'r = [L2, L4)
// 's = [L3, L4)

1.4.2 Higher-Rank Trait Bounds (HRTB)

for<'a> 構文

// 全てのライフタイムについて成立
fn example<F>(f: F)
where
    F: for<'a> Fn(&'a i32) -> &'a i32,
{
    let x = 5;
    let y = f(&x);
    println!("{}", y);
}

形式的意味

∀ 'a. F: Fn(&'a i32) -> &'a i32

(任意のライフタイム'aについて、
  Fは&'a i32を受け取り&'a i32を返す関数)

---

1.5 早期束縛 vs 遅延束縛

1.5.1 Early-bound Lifetimes

fn foo<'a>(x: &'a i32) -> &'a i32 {
    //   ^^
    //   早期束縛:関数呼び出し時に決定
    x
}

// 呼び出し側
foo::<'static>(&5);  // 明示的に指定可能

1.5.2 Late-bound Lifetimes

// HRTBで使用
fn bar(f: for<'a> fn(&'a i32)) {
    //        ^^^^^^^
    //        遅延束縛:使用時に決定
}

違い

Early-bound:
- 関数のジェネリクスパラメータとして
- 明示的に指定可能
- 単相化される

Late-bound:
- for<'a> で導入
- 呼び出し元で自動推論
- 柔軟性が高い

---

1.6 ライフタイムと型の関係

1.6.1 ライフタイムパラメータの位置

struct Foo<'a, T> {
    //     ^^  ^
    //     |   型パラメータ
    //     ライフタイムパラメータ

    x: &'a T,
}

// ライフタイムは型の一部
type Alias<'a> = &'a i32;

1.6.2 Associated Type とライフタイム

trait Iterator {
    type Item;  // 関連型

    fn next(&mut self) -> Option<Self::Item>;
}

// ライフタイム付き関連型
trait LendingIterator {
    type Item<'a> where Self: 'a;

    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}

---

1.7 'static の特殊性

1.7.1 'static の2つの意味

1. 静的ライフタイム

let s: &'static str = "hello";
// プログラム全体で有効

2. 'static トレイト境界

fn foo<T: 'static>(x: T) {
    // T は 'static ライフタイムの参照を含まない
    // または、T自体が所有型
}

1.7.2 'static の形式的定義

'static = [program_start, program_end)

任意のライフタイム 'a について:
'static: 'a  ('static は全てのライフタイムをoutlive)

---

1.8 まとめ

1.8.1 理論的基盤

ライフタイムシステムは以下に基づく:

  • 領域ベース型システム(Tofte-Talpin)
  • サブタイピング(順序関係)
  • Variance(型構築子の振る舞い)
  • 制約ベース推論(最小不動点)
  • 1.8.2 実用的な理解

  • ライフタイムは時間区間
  • 包含関係がサブタイピングを決定
  • Varianceは安全性を保証
  • コンパイラが制約を解く

---

練習問題

問題1

以下の制約を満たすライフタイムの最小解を求めなさい。

'a ⊇ 'b
'b ⊇ 'c
'c ⊇ [L5, L10)

問題2

なぜ &'a mut T が invariant である必要があるか、反例を用いて説明しなさい。

問題3

以下のコードでライフタイム推論がどのように働くか説明しなさい。

fn foo<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
    if x > y { x } else { y }
}

---

次の章へ

Chapter 2: 複雑なライフタイム問題へ →