Skip to content

Instantly share code, notes, and snippets.

@gyu-don
Created June 1, 2025 15:37
Show Gist options
  • Save gyu-don/fc57013d5e5885db18764f0588f65b7d to your computer and use it in GitHub Desktop.
Save gyu-don/fc57013d5e5885db18764f0588f65b7d to your computer and use it in GitHub Desktop.
o3 vs Opus 4 型システム不健全性バグ解説

2025-06-02のChatGPT PlusとClaude Proを使用。ChatGPT Plusはo3モデル、Claude ProはOpus 4を「じっくり考える」を有効にして答えさせた。

思考時間はo3は1分40秒程度に対し、Opus 4は数秒程度だたt

概要

Issue #25860 は 「入れ子になった参照の暗黙境界(implied bounds)と参照の共変性(variance)が組み合わさると、安全コードでライフタイムが破られ得る」 という、Rust コンパイラの根本的な欠陥を報告しています。2015 年に報告されて以来 10 年近く未解決で、I-unsound ラベルが付いたまま “known-bug” テストで監視されている最古参バグの一つです(GitHub)。


何が起きるのか

static UNIT: &'static &'static () = &&();

fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v }

fn bad<'a, T>(x: &'a T) -> &'static T {
    let f: fn(&'static &'a (), &'a T) -> &'static T = foo;
    f(UNIT, x)
}

上の badunsafe を一切書かずに &'a T から &'static T を作り出しています(GitHub)。

  1. 暗黙境界 (implied bounds) &'a &'b T という型が「整形式」であるためには 'b: 'a が暗黙に成り立つ、とコンパイラは仮定します。
  2. 共変性 (variance) 参照はライフタイムについて共変なので、&'static &'a ()&'a &'static () に型変換できてしまいます。
  3. 関数ポインタの再型付け 上記 2 つを組み合わせると、foof安全に キャストできたように見えてしまい、結果として &'static T が返ります。

この流れを最初に整理したのが aturon 氏のコメントで、「foo'b: 'a を前提にしているが、その前提はポインタにキャストした時点では検証されない」というのが要点です(GitHub)。


なぜコンパイラは防げなかったのか

  • 暗黙境界の導出タイミング… 現行の型検査器は「型が出現した瞬間」に暗黙境界を記録しますが、 あとでライフタイム・パラメータが入れ替わる可能性 までは追跡していません。
  • 関数ポインタの共変性… 関数ポインタ自体も引数・戻り値について共変/反変の規則があり、 それが暗黙境界チェックより優先して適用されるため、境界違反が見逃されます。

現在の状況 (2025 年 6 月時点)

項目 状態
コンパイラの挙動 実例プログラムは 現在はコンパイルエラー になります(より厳しい「外側 ⟶ 内側」ライフタイム検査が追加されたため)。ただし検査は 経験的防御策 であり、理論的には穴が残っています。
Issue ラベル I-unsound, A-type-system, A-variance, S-bug-has-test
恒久的な解決策 新 Trait Solver (-Znext-solver / Chalk 系) で「必要な暗黙境界を 使用時に 証明する」仕組みに置き換え、同種の欠陥を包括的に防ぐ計画。実装は段階的に進行中で、本件もそのスコープに含まれています(Hacker News)。

Rust 中級者向けヒント

  1. 「入れ子参照の暗黙境界」は自分で明示するクセを付ける

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

    明示しておけば、少なくとも誤った型変換時に早期エラーが期待できます。

  2. 関数ポインタの型キャストは最小限に ライフタイムを含む型パラメータがある場合、as キャストや transmute での再型付けは極力避ける。

  3. #![feature(lint_reasons)] などで “unknown-lint:rust-cviolation” を無視しない 近年の nightly には「既知の未修正バグを踏むコード」を叱る Lint が徐々に入っています。


まとめ

  • Issue #25860 は 暗黙境界 × 共変性 という Rust 型システムの設計上の複合問題。
  • 実例は現在コンパイル不可になったものの、理論上の穴は残っている ため “unsound” ラベルは継続。
  • 真の解決には 次世代 Trait Solver と暗黙境界の「使用時」検証 が必要とされ、Rust コンパイラ開発の主要テーマの一つになっています。

これで「なぜ問題が起こるのか」「現状どう対処されているか」「どこがまだ未解決か」がイメージしやすくなれば幸いです。

指定されたGitHub issueの内容を確認させていただきます。## Rust Issue #25860: 変性と暗黙的境界による健全性の穴

このissueは、Rustの型システムにおける重要な健全性(soundness)の問題を扱っています。2015年から報告されており、部分的な修正は行われていますが、根本的な問題は現在も残っています。

問題の概要

このissueは、以下の2つの機能の相互作用によって発生します:

  1. 変性(Variance) - 特に関数の引数における反変性(contravariance)
  2. 暗黙的境界(Implied bounds) - ネストした参照における生存期間の暗黙的な制約

これらが組み合わさることで、安全なRustコードで不正なメモリアクセスが可能になってしまいます。

具体的な問題コード

// 元の例(現在は部分的に修正されコンパイルエラーになる)
fn foo<'a, 'b, T>(_: &'a &'b (), v: &'b T) -> &'a T { v }

fn bad<'c, 'd, T>(x: &'c T) -> &'d T {
    let f: fn(&'d &'d (), &'c T) -> &'d T = foo;
    f(&(), x)
}

より現代的な例(まだ動作する問題のあるコード):

fn foo<'a, 'b, T>(_false_witness: Option<&'a &'b ()>, v: &'b T) -> &'a T { 
    v 
}

fn bad<'c, 'd, T>(x: &'c T) -> &'d T {
    // 反変性を使って関数をキャストし、'c: 'd の証明を回避
    let f: fn(Option<&'d &'d ()>, &'c T) -> &'d T = foo;
    f(None, x)
}

fn main() {
    fn inner() -> &'static String {
        bad(&format!("hello"))  // 一時的な値への静的参照を作成!
    }
    let x = inner();
    println!("x: {}", x);  // use-after-free!
}

なぜこれが問題なのか

  1. 暗黙的境界の仮定

    • &'a &'b () という型は、暗黙的に 'b: 'a('bは'aより長生きする)を要求します
    • Rustはこの制約を関数内で仮定して使用します
  2. 反変性による型の変換

    • 関数の引数は反変(contravariant)なので、より制限的な型を受け入れる関数として扱えます
    • &'a &'b ()&'d &'d () として扱うことができてしまいます
  3. 健全性の破壊

    • 上記の組み合わせにより、任意の生存期間を別の生存期間に「昇格」できてしまいます
    • これにより、解放済みメモリへの参照を作成できます

技術的な詳細

問題の核心は、高階ランク型(higher-ranked types)における where節の扱いにあります:

// 現在の型(暗黙的)
fn foo<'a, 'b, T>(&'a &'b (), &'b T) -> &'a T

// 実際に必要な型(明示的なwhere節付き)
fn foo<'a, 'b, T>(&'a &'b (), &'b T) -> &'a T 
    where 'b: 'a  // この制約が関数ポインタに保持されない

現在の状況と今後

  • 部分的な修正: 非高階ランク関数ポインタについては修正済み(#129021)
  • 根本的な解決: 次世代トレイトソルバーとwhere節のバインダーサポートが必要
  • 回避策: 明示的にライフタイム境界を書くことで問題を回避できる場合があります

中級者向けの教訓

  1. 変性を理解する: 特に関数の引数における反変性は直感に反する動作をすることがあります
  2. 暗黙的境界に注意: ネストした参照は暗黙的な生存期間の制約を持ちます
  3. 型システムの限界: 現在のRustでも、まだ完全に解決されていない健全性の問題が存在します

この問題は、Rustの型システムの複雑さと、健全性を保証することの難しさを示す好例です。日常的なコーディングで遭遇することは稀ですが、型システムの深い理解には重要な事例となっています。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment