rust-unsafe-ffi について
rust-unsafe-ffi は、unsafe コード、ローメモリ、C ABI 境界を扱う Rust 開発者向けのスキルです。安全性の契約を明文化し、unsafe の範囲や所有権を整理して検証します。
主な機能
- 01unsafe 操作の根拠付けと分離
- 02安全性契約の文書化
- 03ローメモリの慎重な操作
- 04レイアウト、Pin、ABI の明確化
- 05限定的な FFI 境界の設計
- 06Miri とプラットフォームテストによる検証
活用シーン
- 01ローポインターコードのレビュー
- 02C ABI バインディングの構築・監査
- 03FFI 境界をまたぐ所有権の確認
- 04unsafe なメモリ不変条件のテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ full-stack-skills/ rust-skills --skill rust-unsafe-ffiインストール後、Claude Code で/rust-unsafe-ffiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
agents/openai.yamlexamples/examples.mdexamples/golden-unsafe/Cargo.tomlexamples/golden-unsafe/src/lib.rsreferences/references.md詳細
Rust Unsafe と FFI
unsafe は証明責任として扱います。unsafe ブロックによって特定の操作が許可されますが、妥当性、エイリアシング、初期化、ライフタイム、データ競合、レイアウト、ABI に関する要件が緩和されるわけではありません。
対象範囲と振り分け
生ポインター、NonNull、unsafe 関数とトレイト、共用体、MaybeUninit、ManuallyDrop、レイアウト、ピン留め、アロケーター、手動の Send または Sync 実装、C ABI バインディング、コールバック、ハンドル、所有権の移転には、このスキルを使用します。
通常の所有権設計は rust-stable、並行アーキテクチャは rust-concurrency、UniFFI で生成された多言語バインディングは rust-uniffi-building、一般的なビルドスクリプトは rust-cargo-build、レビュー報告は rust-code-review に振り分けてください。
ワークフロー
1. unsafe 操作を正当化し、隔離する
安全な Rust では表現できない操作を特定します。必要な動作を維持できる場合は、監査済みのクレートまたは標準ライブラリの抽象化を優先します。unsafe ブロックを小さく保ち、次を有効にします。
#![deny(unsafe_op_in_unsafe_fn)]
unsafe fn でも、各 unsafe 操作を明示的な unsafe {} ブロックに記述する必要があります。すべての不変条件を内部で確立し維持できる場合に限り、安全なラッパーを公開します。
2. コードを書く前に安全性の契約を記述する
次の点を文書化します。
- ポインターの由来、非 null 性、アラインメント、参照解除可能なバイト範囲。
- 指す先の型に求められる初期化状態と妥当性。
- アクセス期間全体にわたるエイリアシングと変更の規則。
- ライフタイムと所有権の移転。値を破棄または解放する担当者も含みます。
- スレッド安全性と再入性の要件。
- ABI、レイアウト、呼び出し規約、整数幅、エラー規約。
- 境界を越える際のパニックまたは外部例外の扱い。
公開する unsafe API には # Safety ドキュメントを使用し、証明を示す箇所には // SAFETY: コメントを付けます。
3. 生メモリ操作は慎重に実装する
pub unsafe fn read_i32(ptr: *const i32) -> i32 {
// SAFETY: The caller guarantees that ptr is aligned, initialized,
// dereferenceable for one i32, and not concurrently mutated.
unsafe { ptr.read() }
}
ptr.addは、同じ割り当てオブジェクト内またはその 1 つ先までの場合にのみ使用します。- アラインされた読み取りとアラインされていない読み取りを区別し、アラインされていないパックフィールドへの参照を作成しないでください。
- 値が未初期化の可能性がある場合は
MaybeUninit<T>を使用し、破棄またはassume_initの前に、初期化済みの要素を正確に追跡します。 transmuteよりもfrom_ne_bytes、レイアウトを確認したポインターキャスト、または明示的なフィールド変換を優先します。- 所有権の曖昧さや二重破棄のリスクを隠すために
ManuallyDropを使用しないでください。
4. レイアウトとピン留めを正確に定義する
- C 互換の構造体レイアウトには
#[repr(C)]を、共有する列挙型には明示的な repr を使用します。 #[repr(transparent)]は、文書化されたフィールドの制約を満たす場合にのみ使用します。- パディングは未初期化の可能性があるものとして扱います。構造体を生バイトのコピーでシリアライズしないでください。
Pin<P>が指す先を保護するのは、ピン留めの契約に従う場合に限られます。通常のUnpinデータを移動不可にするものではなく、それだけで自己参照が健全になるわけでもありません。- 生の自己参照ポインターよりも、確立された射影ヘルパーや構造的ピン留めのパターンを優先します。
5. 境界を限定した FFI を構築する
Edition 2024 では、該当する場合に unsafe な extern ブロックと unsafe 属性が必要です。
use std::ffi::{c_char, CStr};
unsafe extern "C" {
fn foreign_name() -> *const c_char;
}
pub fn name() -> Option<String> {
// SAFETY: The foreign contract promises either null or a valid,
// NUL-terminated string that remains alive for this call.
let ptr = unsafe { foreign_name() };
(!ptr.is_null()).then(|| {
// SAFETY: Non-nullness and termination follow from the contract above.
unsafe { CStr::from_ptr(ptr) }.to_string_lossy().into_owned()
})
}
- Rust の整数幅を推測せず、
std::ffiの C 型または生成されたバインディングを使用します。 - nullable ポインター、長さ、所有権、エラーコードを単一の境界で変換します。
- 選択した ABI が明示的に許可していない限り、C ABI を越えてアンワインドしないようにします。必要に応じてコールバック内でパニックを捕捉します。
- コールバックのライフタイム、スレッド、キャンセル、登録解除の動作を明確にします。
- 同じアロケーター領域内で割り当てと解放を対応させます。
6. 証明を検証する
該当する検証を実行します。
cargo fmt --all --check
cargo check --workspace --all-targets --all-features
cargo test --workspace --all-targets --all-features
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo miri test
また、対応している場合はサニタイザーや Valgrind、ABI レイアウトのアサーション、C 側の統合テスト、複数の最適化レベル、およびサポート対象のすべてのオペレーティングシステムとアーキテクチャも使用します。Miri は任意の外部コードを実行しないため、Rust 側のメモリ契約をテストする際は外部呼び出しを分離するかモック化します。
メモリ型の詳細については、Unsafe と FFI のリファレンスを参照してください。代表的な境界については、実行シナリオを参照してください。
レビューチェックリスト
- unsafe は必要最小限であり、ローカルな根拠がありますか?
- 各安全性契約は、有効性、エイリアス、ライフタイム、レイアウト、並行性を網羅していますか?
- 安全な呼び出し側が、ラッパーの前提に違反する可能性はありますか?
- 部分的な初期化や、失敗時のクリーンアップは適切ですか?
- FFI をまたぐ所有権と解放は対称になっていますか?
- パニックや外部のアンワインドが ABI 境界を越える可能性はありますか?
- 手動で実装した
SendとSyncは、すべてのフィールドとコールバックについて証明されていますか? - テストでは、null、空、最大値、アラインメント不一致、エラー、コールバック、シャットダウンの各経路を検証していますか?
完了条件
- 呼び出し側と実装側の安全性に関する義務をすべて文書化してください。
- unsafe ブロックを最小限に抑え、安全なラッパーを安全な Rust から誤用できないようにしてください。
- ABI の型、レイアウト、所有権、エラー、アンワインドの動作を検証してください。
- 該当する場合は Miri を実行し、外部境界については実際のプラットフォームでテストしてください。
- ローカルで検証できなかった性質があれば記録してください。
上流のソース
データのプライバシー
このスキルは、ユーザーデータを収集、保存、送信しません。許可なく、独自のヘッダー、生成されたバインディング、クラッシュダンプ、メモリの内容を外部サービスに送信しないでください。