Unsafe Rustと外部関数インターフェース (FFI)
Unsafe Rustを完全マスター:5つの超能力、生ポインタの参照外し、FFIによるC/C++ライブラリ呼び出し、安全な抽象化の設計。
第3部:上級システム開発と実践Rust の第2回。Rustは厳格な静的メモリ安全性を誇りますが、OSカーネルやハードウェア制御、レガシーC/C++資産との連携、極限のパフォーマンス追求といったシステムプログラミングの現場では、コンパイラが安全性を証明できない低レベル操作が必要になります。
Rustは、この現実的な要求に対して unsafe キーワードを用意しています。
graph TD
A["Unsafe Rust"] --> B["5つのUnsafeスーパーパワー"]
B --> B1["生ポインタの参照外し (*const T, *mut T)"]
B --> B2["unsafe関数・外部FFI関数の呼び出し"]
B --> B3["Unsafeトレイトの実装"]
B --> B4["可変静的変数 (static mut) へのアクセス"]
B --> B5["共用体 (union) のフィールド操作"]
A --> C["C言語とのFFI連携"]
A --> D["安全な抽象化によるカプセル化"]
10歳でもわかる説明(ELI5):溶接工の安全シールド#
- Safe Rust:リビングの照明スイッチのようなものです。配線が全て絶縁保護されているため、誰でも100%安全にボタンを押せます。
- Unsafe Rust:高圧配電盤を開けて作業するような状態です。コンパイラは「ここから先は通電状態をチェックできないため、プロとして全責任を持って操作してください!」と警告します。
unsafeはボローチェッカーを無効化するわけではなく、厳密に定められた 5つの低レベル操作 の実行を許可する仕組みです。
1. 生ポインタ(Raw Pointers: *const T, *mut T)#
通常の参照(&T, &mut T)と異なり、生ポインタには以下の特徴があります:
- 借用規則を無視できる(同一アドレスに対する複数の可変ポインタを作成可能)。
- 有効なメモリを指している保証がない(
nullや無効なアドレスを指す可能性がある)。 - 自動的なメモリ解放(
Drop)が行われない。
[!NOTE] 生ポインタを 作成すること自体は完全に安全(Safe) です。メモリを読み書きするために 参照外し(
*pointer)を行うときのみunsafeブロックが必要です。
src/main.rs
fn main() {
let mut num = 42;
// 参照から生ポインタを作成(安全)
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;
// 生ポインタの参照外し(Unsafe)
unsafe {
println!("r1 が指す値: {}", *r1);
*r2 = 100;
println!("r2 変更後の値: {}", *r2);
}
}rust2. Unsafe関数の呼び出しと安全な抽象化#
Rustにおける王道パターンは、Unsafeな操作を内部に隠蔽し、安全で使いやすい公開API(Safe Abstraction)として包み込むことです。
標準ライブラリの split_at_mut(1つのスライスを重なりのない2つの可変スライスに分割する関数)が良い例です:
src/main.rs
use std::slice;
fn split_at_mut<T>(values: &mut [T], mid: usize) -> (&mut [T], &mut [T]) {
let len = values.len();
let ptr = values.as_mut_ptr();
assert!(mid <= len);
unsafe {
(
slice::from_raw_parts_mut(ptr, mid),
slice::from_raw_parts_mut(ptr.add(mid), len - mid),
)
}
}
fn main() {
let mut vector = vec![1, 2, 3, 4, 5, 6];
let (left, right) = split_at_mut(&mut vector, 3);
println!("Left: {left:?}, Right: {right:?}");
}rust3. C言語とのFFI連携(Foreign Function Interface)#
Rustは標準C ABIに準拠しているため、オーバーヘッドなしでC言語の関数を直接呼び出すことができます:
C標準ライブラリの関数を呼び出す#
src/main.rs
use std::ffi::c_int;
// 外部C ABI関数の宣言
extern "C" {
fn abs(input: c_int) -> c_int;
}
fn main() {
unsafe {
let result = abs(-42);
println!("C言語標準ライブラリからの絶対値: {result}");
}
}rustRustコードをC/C++向けにエクスポートする#
Rustで記述したロジックを共有ライブラリ(.so, .dylib, .dll)としてコンパイルし、CやPythonから呼び出すことが可能です:
#[no_mangle]
pub extern "C" fn rust_add(a: i32, b: i32) -> i32 {
a + b
}rust#[no_mangle]:コンパイラによるシンボル名マングル(名前の難読化・改変)を無効化します。extern "C":C言語の関数呼び出し規約を使用します。
Unsafeコードのベストプラクティス#
- Unsafeスコープの最小化:
unsafeブロックは必要最小限の行数に留めます。 - 不変条件の明記:なぜそのUnsafeコードが安全なのかコメントで根拠を記述します。
- Miriツールによるテスト:
cargo miri testを実行し、未定義動作(Undefined Behavior)やメモリリークを静的・動的に検出します。
まとめ#
unsafeはOS開発やハードウェア制御、FFIに必要な5つの低レベル操作を解放します。- 生ポインタ(
*const T,*mut T)により、メモリへの直接アクセスが可能です。 - FFIによって既存のC/C++ライブラリと双方向で高速に連携できます。
- 常にUnsafeなコアロジックをSafeなAPIでカプセル化することが推奨されます。