このページの目次
unsafe ブロックと Soundness(健全性)契約
unsafeキーワードはセキュリティチェックを「無効にする」のではなく、コンパイラが保証していたセキュリティ契約をプログラマに委譲します。無安全なポインタのデリファレンス、unsafe 関数の呼び出し、ミュータブルな静的変数へのアクセスが可能になりますが、呼び出し側がどう行動しても未定義動作(UB)を引き起こさないことを保証する必要があります。unsafe コードを書く本当の難しさは「書けること」ではなく、それを安全な抽象ラッパーで包むことです。
unsafe は「すべてのセキュリティチェックを無効にする」ものではない
unsafe {} ブロックは 5 つの能力のみを有効にし、それ以外の Rust のセキュリティチェックは依然として有効です:
- 無安全なポインタのデリファレンス (
*const T,*mut T) unsafe fnの呼び出しstatic mut変数のアクセスまたは変更unsafe trait(Send、Sync、GlobalAllocなど)の実装unionのフィールドへのアクセス
借用チェッカーは unsafe ブロック内でも有効です——2 つの &mut T を同じデータを指すように作成することはできません。これは UB であり、コンパイラは unsafe 内でもこれを拒否します。
unsafe を理解するための鍵:それは「すべてのセキュリティ責任を引き受ける」ということではなく、「コンパイラが自動検証できない部分の責任を引き受ける」ということです。
Soundness: 安全の境界
unsafe fn のシグネチャは契約を定義します。呼び出し側は事前条件を満たす必要があり、そうでなければ UB が発生する可能性があります。標準ライブラリにはこのようなパターンが至る所にあります:
// 外部は安全、内部は unsafe — 呼び出し側は引数を正しく渡すだけでよく、unsafe を書く必要はありません
assert が通過した場合、以下の unsafe 操作は sound(健全)です——mid <= len が 2 つのスライスが重ならないことを保証します。assert が失敗した場合、直接 panic します——unsafe 領域には入りません。これは標準ライブラリで最も一般的なパターンです:安全なコードが条件を検証し、unsafe コードはその条件が満たされた前提下で実行されます。
重要:unsafe コードの soundness は、unsafe ブロック内のロジックだけでなく、外部の安全なコードが維持する不変条件にも依存しています。あなたが書いた unsafe コードが何らかのデータ構造の不変条件(例:len <= cap)を仮定している場合、そのデータ構造を変更する外部の安全なコードはすべて、この不変条件を維持しなければなりません。
UB(未定義動作): 避けるべきリスト
- 空ポインタ、ダングリングポインタ、アラインメントされていないポインタのデリファレンス
- 借用ルールの違反(unsafe 内であっても)
- データ競合
- 間違った ABI で関数を呼び出す
- 不正な値の生成:
boolが 0 でも 1 でもない、参照が空、charがサロゲートである - FFI 境界を越えるアンワインド(
extern "C"関数内での panic)
Miri: UB の自動検出
Miri は MIR レベルで Rust を解釈実行し、provenance と借用の状態を追跡します:
use-after-free、境界外アクセス、データ競合、アラインメント違反を検出できます。unsafe を含むすべての crate は Miri を実行すべきです——soundness を証明することはできませんが、明らかな UB を発見できます。
参考
- Rustonomicon: 「What Unsafe Can Do」、Soundness の要件
- Rust Reference: 未定義動作とみなされる挙動
- Miri: github.com/rust-lang/miri
Keywords: unsafe, soundness, UB, Miri, safety contract, invariant, raw pointer