2025-06-02のChatGPT PlusとClaude Proを使用。ChatGPT Plusはo3モデル、Claude ProはOpus 4を「じっくり考える」を有効にして答えさせた。
思考時間はo3は1分40秒程度に対し、Opus 4は数秒程度だたt
Rust中級者に向けて rust-lang/rust#25860 を解説して下さい
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)
}
上の bad
は unsafe を一切書かずに &'a T
から &'static T
を作り出しています(GitHub)。
&'a &'b T
という型が「整形式」であるためには 'b: 'a
が暗黙に成り立つ、とコンパイラは仮定します。&'static &'a ()
を &'a &'static ()
に型変換できてしまいます。foo
を f
に 安全に キャストできたように見えてしまい、結果として &'static T
が返ります。この流れを最初に整理したのが aturon 氏のコメントで、「foo
は 'b: 'a
を前提にしているが、その前提はポインタにキャストした時点では検証されない」というのが要点です(GitHub)。
項目 | 状態 |
---|---|
コンパイラの挙動 | 実例プログラムは 現在はコンパイルエラー になります(より厳しい「外側 ⟶ 内側」ライフタイム検査が追加されたため)。ただし検査は 経験的防御策 であり、理論的には穴が残っています。 |
Issue ラベル | I-unsound , A-type-system , A-variance , S-bug-has-test |
恒久的な解決策 | 新 Trait Solver (-Znext-solver / Chalk 系) で「必要な暗黙境界を 使用時に 証明する」仕組みに置き換え、同種の欠陥を包括的に防ぐ計画。実装は段階的に進行中で、本件もそのスコープに含まれています(Hacker News)。 |
「入れ子参照の暗黙境界」は自分で明示するクセを付ける
fn foo<'a, 'b>(x: &'a &'b i32) where 'b: 'a { … }
明示しておけば、少なくとも誤った型変換時に早期エラーが期待できます。
関数ポインタの型キャストは最小限に
ライフタイムを含む型パラメータがある場合、as
キャストや transmute
での再型付けは極力避ける。
#![feature(lint_reasons)]
などで “unknown-lint:rust-cviolation” を無視しない
近年の nightly には「既知の未修正バグを踏むコード」を叱る Lint が徐々に入っています。
これで「なぜ問題が起こるのか」「現状どう対処されているか」「どこがまだ未解決か」がイメージしやすくなれば幸いです。
指定されたGitHub issueの内容を確認させていただきます。## Rust Issue #25860: 変性と暗黙的境界による健全性の穴
このissueは、Rustの型システムにおける重要な健全性(soundness)の問題を扱っています。2015年から報告されており、部分的な修正は行われていますが、根本的な問題は現在も残っています。
このissueは、以下の2つの機能の相互作用によって発生します:
これらが組み合わさることで、安全な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!
}
暗黙的境界の仮定
&'a &'b ()
という型は、暗黙的に 'b: 'a
('bは'aより長生きする)を要求します反変性による型の変換
&'a &'b ()
を &'d &'d ()
として扱うことができてしまいます健全性の破壊
問題の核心は、高階ランク型(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 // この制約が関数ポインタに保持されない
この問題は、Rustの型システムの複雑さと、健全性を保証することの難しさを示す好例です。日常的なコーディングで遭遇することは稀ですが、型システムの深い理解には重要な事例となっています。