Chapter 4: Advanced Ownership - Unsafe、FFI、Pin

この章の目標

この章を読み終えると、以下のことが理解できるようになります:

  • Unsafe Rustの5つの超能力と責任
  • FFI(Foreign Function Interface)での所有権管理
  • PinとUnpinの理論と実践
  • 自己参照構造体の安全な実装
  • メモリレイアウトと repr の使い方
  • Variance(変性)と高度なライフタイム
  • なぜAdvanced Ownershipを学ぶのか

    Safe Rustの所有権システムは強力ですが、以下のような場合には不十分です:

  • 低レベルシステムプログラミング: OS、ドライバ、組み込み
  • C/C++とのインターフェース: 既存ライブラリの活用
  • 高性能データ構造: Vecの内部実装のような最適化
  • 非同期プログラミング: Future、async/awaitの内部実装

これらの分野では、unsafeを使って所有権システムの制約を一時的に解除し、より柔軟な実装を行います。しかし、unsafeは責任を伴います。この章では、unsafeを安全に使う方法を学びます。

1. Unsafe Rustの5つの超能力

1.1 Unsafeとは何か

Rustの型システムは保守的(conservative)です。つまり、安全なコードを拒否することもあります

┌─────────────────────────────────────────────────────────┐
│         コンパイラの判断                                  │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  全てのコード                                            │
│  ├─ 確実に安全   → Safe Rustで許可                      │
│  ├─ 確実に危険   → コンパイルエラー                      │
│  └─ 安全かも?   → Safe Rustで拒否、unsafeで許可        │
│                                                         │
│  コンパイラは「疑わしきは罰する」                          │
│  → unsafeで「私が責任を持つ」と宣言                       │
│                                                         │
└─────────────────────────────────────────────────────────┘

1.2 Unsafeの5つの超能力

unsafeブロックでは、以下の5つのことができます:

1. 生ポインタのデリファレンス

let x = 5;
let raw = &x as *const i32;  // 生ポインタの作成(safeでOK)

unsafe {
    println!("{}", *raw);  // デリファレンスはunsafe
}

なぜunsafe?

  • 生ポインタはnullかもしれない
  • ダングリングポインタかもしれない
  • アライメントが正しくないかもしれない

Safe Rustとの違い

// Safe Rust: コンパイラが保証
let x = 5;
let r = &x;  // 常に有効
println!("{}", *r);  // 安全

// Unsafe Rust: プログラマが保証
let raw = &x as *const i32;
unsafe {
    // 有効性は自分で確認
    println!("{}", *raw);
}

2. Unsafe関数/メソッドの呼び出し

unsafe fn dangerous() {
    // 危険な操作
}

fn main() {
    unsafe {
        dangerous();  // unsafeブロックが必要
    }
}

標準ライブラリの例

use std::slice;

let v = vec![1, 2, 3, 4, 5];
let ptr = v.as_ptr();
let len = v.len();

unsafe {
    // from_raw_partsはunsafe関数
    let slice = slice::from_raw_parts(ptr, len);
    println!("{:?}", slice);
}

3. Mutableなstatic変数へのアクセス

static mut COUNTER: i32 = 0;

fn increment() {
    unsafe {
        COUNTER += 1;  // unsafeが必要
    }
}

なぜunsafe?

  • データ競合の可能性
  • グローバルな可変状態

より安全な代替案

use std::sync::atomic::{AtomicI32, Ordering};

static COUNTER: AtomicI32 = AtomicI32::new(0);

fn increment() {
    COUNTER.fetch_add(1, Ordering::SeqCst);  // Safeで書ける!
}

4. Unsafeトレイトの実装

unsafe trait Foo {
    // 実装に特定の不変条件が必要
}

unsafe impl Foo for i32 {
    // 不変条件を守る責任がある
}

標準ライブラリの例

// Sendトレイト: 値を他のスレッドに送信できる
unsafe impl<T: Send> Send for MyType<T> {}

// Syncトレイト: 複数スレッドから参照を共有できる
unsafe impl<T: Sync> Sync for MyType<T> {}

5. Unionフィールドへのアクセス

union MyUnion {
    f1: u32,
    f2: f32,
}

let u = MyUnion { f1: 1 };

unsafe {
    println!("{}", u.f1);  // どのフィールドが有効か不明
}

使用例

// FFIでC言語のunionを表現
#[repr(C)]
union IpAddr {
    ipv4: [u8; 4],
    ipv6: [u8; 16],
}

1.3 Unsafeの責任

unsafeを使うと、以下を自分で保証する必要があります:

┌─────────────────────────────────────────────────────────┐
│         Unsafeで保証すべきこと                            │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 空間的安全性(Spatial Safety)                       │
│     - 全てのポインタは有効なメモリを指す                   │
│     - バッファオーバーフローなし                          │
│                                                         │
│  2. 時間的安全性(Temporal Safety)                      │
│     - Use-after-freeなし                                │
│     - ダングリングポインタなし                            │
│                                                         │
│  3. スレッド安全性(Thread Safety)                      │
│     - データ競合なし                                     │
│     - 適切な同期                                         │
│                                                         │
│  4. 型安全性(Type Safety)                              │
│     - 無効な値の読み取りなし(例: bool = 2)              │
│     - アライメント要件を満たす                            │
│                                                         │
│  5. 不変条件(Invariants)                               │
│     - データ構造の整合性を保つ                            │
│     - 例: Vecのlen <= capacity                          │
│                                                         │
└─────────────────────────────────────────────────────────┘

2. FFI(Foreign Function Interface)

2.1 FFIとは

FFIは、Rustから他の言語(主にC)の関数を呼ぶ、または呼ばれる仕組みです。

┌─────────────────────────────────────────────────────────┐
│         FFIの2つの方向                                    │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. Rustから他言語を呼ぶ                                 │
│     Rust → C Library                                    │
│     例: zlib、OpenSSL、SQLite                            │
│                                                         │
│  2. 他言語からRustを呼ぶ                                 │
│     C/Python/JavaScript → Rust Library                  │
│     例: PyO3、WASM、Node.jsのネイティブモジュール          │
│                                                         │
└─────────────────────────────────────────────────────────┘

2.2 C関数の呼び出し

基本的な例

// C言語のabs関数を呼ぶ
extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("abs(-3) = {}", abs(-3));
    }
}

注意点

  • extern "C": C言語のABI(Application Binary Interface)を使用
  • 関数呼び出しはunsafe: Rustの不変条件を保証できない

複雑な例:構造体の受け渡し

// C言語の構造体
// struct Point {
//     int x;
//     int y;
// };
// void translate(struct Point* p, int dx, int dy);

#[repr(C)]
struct Point {
    x: i32,
    y: i32,
}

extern "C" {
    fn translate(p: *mut Point, dx: i32, dy: i32);
}

fn main() {
    let mut p = Point { x: 10, y: 20 };

    unsafe {
        translate(&mut p, 5, -3);
    }

    println!("Point: ({}, {})", p.x, p.y);
}

#[repr(C)] の重要性

Rustのデフォルト構造体:
  メモリレイアウトは未定義
  最適化のために並び替えられる可能性

#[repr(C)]付き構造体:
  Cと同じメモリレイアウト
  フィールドの順序が保証される
  パディングもCと同じ

2.3 文字列のFFI

C言語とRustでは文字列の扱いが大きく異なります:

┌─────────────────────────────────────────────────────────┐
│         C vs Rust の文字列                                │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  C言語(char*)                                          │
│  - ヌル終端('\0'で終わる)                              │
│  - UTF-8保証なし                                         │
│  - メモリ管理は手動                                       │
│  - 例: "hello\0"                                         │
│                                                         │
│  Rust(String)                                          │
│  - 長さを保持                                            │
│  - 必ずUTF-8                                            │
│  - 所有権で自動管理                                       │
│  - ヌル文字を含める可能性                                 │
│                                                         │
└─────────────────────────────────────────────────────────┘

CString/CStrの使用

use std::ffi::{CString, CStr};
use std::os::raw::c_char;

extern "C" {
    fn print_string(s: *const c_char);
}

fn main() {
    // Rust String → C char*
    let rust_str = String::from("Hello from Rust!");
    let c_str = CString::new(rust_str).expect("CString::new failed");

    unsafe {
        print_string(c_str.as_ptr());
    }

    // C char* → Rust String
    let c_str_ptr: *const c_char = /* C関数から取得 */;
    unsafe {
        let c_str = CStr::from_ptr(c_str_ptr);
        let rust_str = c_str.to_str().expect("Invalid UTF-8");
        println!("{}", rust_str);
    }
}

注意点

  • CString::new: 内部にヌル文字があるとエラー
  • to_str(): UTF-8検証、失敗の可能性
  • c_str.as_ptr(): ポインタの生存期間に注意

2.4 所有権とFFI

FFIでは、所有権の境界を明確にする必要があります:

// パターン1: Rustが所有、Cは借用
pub extern "C" fn rust_owns(data: *const u8, len: usize) {
    // Rustが確保したメモリをCに渡す(読み取り専用)
    // Rustが解放する責任を持つ
}

// パターン2: Cが所有、Rustは借用
pub extern "C" fn c_owns(data: *mut u8, len: usize) {
    // Cが確保したメモリをRustが使う
    // Cが解放する責任を持つ
}

// パターン3: 所有権の移転
pub extern "C" fn transfer_ownership() -> *mut u8 {
    // Rustで確保したメモリの所有権をCに渡す
    // Cが解放する責任を持つ
    let data = Box::new(42u8);
    Box::into_raw(data)  // 所有権を放棄
}

#[no_mangle]
pub extern "C" fn free_rust_memory(ptr: *mut u8) {
    // Cから返されたメモリをRustで解放
    unsafe {
        let _ = Box::from_raw(ptr);  // 所有権を取り戻してドロップ
    }
}

ベストプラクティス

// 生存期間を明示するOpaque型パターン
pub struct OpaqueHandle {
    _private: [u8; 0],  // ゼロサイズ型
}

#[no_mangle]
pub extern "C" fn create_handle() -> *mut OpaqueHandle {
    let data = Box::new(RealData { /* ... */ });
    Box::into_raw(data) as *mut OpaqueHandle
}

#[no_mangle]
pub extern "C" fn destroy_handle(handle: *mut OpaqueHandle) {
    unsafe {
        let _ = Box::from_raw(handle as *mut RealData);
    }
}

3. PinとUnpin

3.1 なぜPinが必要か

問題: 自己参照構造体はRustで表現できない

struct SelfRef {
    data: String,
    ptr: &String,  // エラー!dataへの参照
}

// コンパイルエラー:
// error[E0106]: missing lifetime specifier

なぜ問題?

移動すると参照が無効に:

┌─────────────────┐
│ SelfRef         │
│ ┌─────────────┐ │
│ │ data: "hi" │ │
│ └─────────────┘ │
│      ↑         │
│      │         │
│ ┌────┴────────┐│
│ │ ptr: &data  ││
│ └─────────────┘│
└─────────────────┘
     ↓ move
┌─────────────────┐
│ SelfRef         │
│ ┌─────────────┐ │  ptr はまだ古いアドレスを指している!
│ │ data: "hi" │ │  → ダングリングポインタ
│ └─────────────┘ │
│ ┌─────────────┐ │
│ │ ptr: &???   │ │
│ └─────────────┘ │
└─────────────────┘

3.2 Pinの導入

Pinは、メモリ上で値を固定します。

use std::pin::Pin;

// Pin<Box<T>>: ヒープ上で固定
let pinned = Box::pin(value);

Pin の保証

┌─────────────────────────────────────────────────────────┐
│         Pin<P<T>> の保証                                  │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Tが!Unpinなら:                                          │
│    - 値はメモリ上で移動しない                            │
│    - ピン留めされた参照からしかアクセスできない            │
│    - ムーブはコンパイルエラー                             │
│                                                         │
│  TがUnpinなら:                                           │
│    - 制約なし(ほとんどの型)                             │
│    - Pin<P<T>>とP<T>は等価                               │
│                                                         │
└─────────────────────────────────────────────────────────┘

3.3 UnpinとMarker Trait

Unpin: 自動実装されるマーカートレイト

// ほとんどの型は自動的にUnpin
impl Unpin for i32 {}
impl<T: Unpin> Unpin for Vec<T> {}
impl<T: Unpin> Unpin for Box<T> {}

!Unpin(Unpinでない): オプトアウト

use std::marker::PhantomPinned;

struct NotUnpin {
    data: String,
    _pin: PhantomPinned,  // Unpinをオプトアウト
}

3.4 自己参照構造体の実装

use std::pin::Pin;
use std::marker::PhantomPinned;

struct SelfRef {
    data: String,
    ptr: *const String,  // 生ポインタを使用
    _pin: PhantomPinned,
}

impl SelfRef {
    fn new(data: String) -> Pin<Box<Self>> {
        let mut boxed = Box::new(SelfRef {
            data,
            ptr: std::ptr::null(),
            _pin: PhantomPinned,
        });

        // ポインタを設定
        let data_ptr: *const String = &boxed.data;
        unsafe {
            let mut_ref = Pin::as_mut(&mut Pin::new_unchecked(&mut boxed));
            Pin::get_unchecked_mut(mut_ref).ptr = data_ptr;
        }

        // ピン留めして返す
        unsafe { Pin::new_unchecked(boxed) }
    }

    fn get_data(self: Pin<&Self>) -> &str {
        &self.data
    }

    fn get_ptr_value(self: Pin<&Self>) -> &str {
        unsafe { &*self.ptr }
    }
}

fn main() {
    let self_ref = SelfRef::new(String::from("hello"));

    println!("data: {}", self_ref.as_ref().get_data());
    println!("ptr: {}", self_ref.as_ref().get_ptr_value());

    // これはコンパイルエラー
    // let moved = *self_ref;  // error: cannot move out of `Pin<Box<SelfRef>>`
}

重要な概念

1. PhantomPinned:
   - Unpinをオプトアウト
   - 値の移動を防ぐ

2. Pin::new_unchecked:
   - ピン留めの安全性を保証するのは自分
   - unsafeでのみ使用

3. Pin::get_unchecked_mut:
   - ピン留めされた値への可変参照を取得
   - 移動しないことを保証する必要がある

3.5 Async/AwaitとPin

非同期プログラミングではPinが不可欠です:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

// Futureは自己参照構造体
struct MyFuture {
    state: String,
    ptr_to_state: *const String,
}

impl Future for MyFuture {
    type Output = ();

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
        // selfはピン留めされている
        // 安全に自己参照を扱える
        Poll::Ready(())
    }
}

なぜFutureにPinが必要?

async fn example() {
    let data = String::from("hello");
    let ptr = &data;  // dataへの参照

    other_async_fn().await;  // ← ここで中断・再開の可能性

    // awaitの前後でメモリ位置が変わると危険
    println!("{}", ptr);
}

↓ コンパイラが生成

struct ExampleFuture {
    data: String,
    ptr: &String,  // 自己参照!
    state: State,
}

// Pinで固定することで安全に

4. メモリレイアウトとrepr

4.1 Rustのデフォルトレイアウト

Rustの構造体は、デフォルトで最適化されたレイアウトを持ちます:

struct Example {
    a: u8,   // 1バイト
    b: u32,  // 4バイト
    c: u16,  // 2バイト
}

// メモリレイアウトは未定義!
// コンパイラが最適化のために並び替える可能性

実際のレイアウト(一例)

┌───┬───────────────┬─────┬─┐
│ a │ padding (3)   │  b  │c│
│1B │     3B        │ 4B  │2│
└───┴───────────────┴─────┴─┘
 0   1               4     8  (オフセット)

合計: 10バイト(パディング含む)

4.2 repr(C)

C言語と同じレイアウトにします:

#[repr(C)]
struct Point {
    x: i32,
    y: i32,
}

レイアウト

┌─────┬─────┐
│  x  │  y  │
│ 4B  │ 4B  │
└─────┴─────┘
 0     4     8

合計: 8バイト
フィールドの順序が保証される

用途

  • FFIでC構造体とやり取り
  • メモリマップドI/O
  • バイナリフォーマットの解析

4.3 repr(transparent)

単一フィールドの構造体を、そのフィールドと同じレイアウトにします:

#[repr(transparent)]
struct Wrapper(u32);

// Wrapperとu32は同じメモリレイアウト
// FFIで型安全性を追加するのに便利

用途

#[repr(transparent)]
struct FileDescriptor(i32);

extern "C" {
    fn close(fd: i32) -> i32;
}

fn close_fd(fd: FileDescriptor) {
    unsafe {
        // Wrapperごと渡せる
        close(fd.0);
    }
}

4.4 repr(packed)

パディングを削除します:

#[repr(packed)]
struct Packed {
    a: u8,
    b: u32,
}

レイアウト

┌───┬─────┐
│ a │  b  │
│1B │ 4B  │
└───┴─────┘
 0   1     5

合計: 5バイト(パディングなし)

注意点

  • アライメント要件違反の可能性
  • パフォーマンスが悪化する可能性
  • 生ポインタでアクセスすべき

#[repr(packed)]
struct Packed {
    a: u8,
    b: u32,
}

let p = Packed { a: 1, b: 2 };

// これは危険!
// let r = &p.b;  // warning: unaligned reference

// 正しい方法
let b = unsafe {
    std::ptr::addr_of!(p.b).read_unaligned()
};

5. Variance(変性)

5.1 変性とは

変性は、ジェネリック型のライフタイムの関係性を定義します。

// 'aが'bより長生きする場合
'a: 'b

// &'a Tは&'b Tに代入できるか?

5.2 3種類の変性

┌─────────────────────────────────────────────────────────┐
│         変性の種類                                        │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  共変(Covariant)                                       │
│    'a: 'b なら T<'a> を T<'b> に代入可能                 │
│    例: &'a T, Box<T>, Vec<T>                            │
│                                                         │
│  反変(Contravariant)                                   │
│    'a: 'b なら T<'b> を T<'a> に代入可能(逆)           │
│    例: fn(&'a T)(関数の引数)                           │
│                                                         │
│  不変(Invariant)                                       │
│    代入不可                                              │
│    例: &'a mut T, Cell<T>                               │
│                                                         │
└─────────────────────────────────────────────────────────┘

5.3 共変の例

fn example<'a>(s: &'a str) -> &'a str {
    s
}

fn main() {
    let long_lived = String::from("long");

    {
        let short_lived = String::from("short");

        // 'staticは全てのライフタイムより長い
        let static_str: &'static str = "static";

        // 共変: &'static strを&'aに代入可能
        let result: &str = example(static_str);
    }
}

5.4 不変の例(可変参照)

fn example<'a, 'b>(x: &'a mut i32, y: &'b mut i32) {
    // これは不可能
    // let z: &'a mut i32 = y;  // エラー!
}

// なぜ不変?
// もし共変なら:
let mut x = 5;
{
    let mut short = 10;
    let long_ref: &'long mut i32 = &mut x;
    let short_ref: &'short mut i32 = &mut short;

    // もし共変なら、これが許可される
    // let long_ref = short_ref;  // 'short → 'long
    // short_refがドロップされた後もlong_refが使える
    // → ダングリングポインタ!
}

6. Real-World Case Studies

6.1 Mozilla Firefox - Stylo

背景: FirefoxのスタイルエンジンをRustで書き直し

所有権の活用

// CSSスタイルシートの並列処理
struct StyleSheet {
    rules: Vec<Rule>,
}

impl StyleSheet {
    fn parallel_process(&self) {
        self.rules.par_iter()  // Rayon
            .map(|rule| process_rule(rule))
            .collect()
    }
}

// 不変参照を複数スレッドで共有
// データ競合は所有権システムで防止

成果

  • パフォーマンス向上(最大30%)
  • セキュリティバグの減少
  • メモリ使用量の削減

6.2 Cloudflare - Pingora

背景: NginxからRustベースのProxyに移行

FFIの活用

// OpenSSLとのFFI
extern "C" {
    fn SSL_read(ssl: *mut SSL, buf: *mut c_void, num: c_int) -> c_int;
    fn SSL_write(ssl: *mut SSL, buf: *const c_void, num: c_int) -> c_int;
}

// Rust側で安全なラッパー
pub struct SslStream {
    ssl: *mut SSL,
}

impl SslStream {
    pub fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let ret = unsafe {
            SSL_read(self.ssl, buf.as_mut_ptr() as *mut c_void, buf.len() as c_int)
        };
        if ret < 0 {
            Err(io::Error::last_os_error())
        } else {
            Ok(ret as usize)
        }
    }
}

成果

  • メモリ使用量70%削減
  • CPU使用率削減
  • セキュリティの向上
  • まとめ

    Unsafe Rustの原則

  • 最小化: unsafeブロックは可能な限り小さく
  • カプセル化: unsafeをsafe APIでラップ
  • 文書化: 不変条件を明確に記述
  • テスト: 徹底的にテスト(Miriツールの使用)
  • Pinの重要性

  • 自己参照構造体の安全な実装
  • Async/Awaitの基盤
  • ゼロコスト抽象化
  • FFIのベストプラクティス

  • #[repr(C)]で明示的なレイアウト
  • 所有権の境界を明確に
  • エラーハンドリングを忘れずに
  • Opaqueポインタパターンを使用
  • 次のステップ

    この章で学んだ高度な概念は、以下の分野で活用されます:

  • システムプログラミング
  • 組み込み開発
  • ハイパフォーマンスコンピューティング
  • 非同期プログラミング
  • ---

    参考文献

  • The Rustonomicon
- https://doc.rust-lang.org/nomicon/ - Unsafe Rustの公式ガイド

  • Pin and Suffering
- https://fasterthanli.me/articles/pin-and-suffering - Pinの詳細解説

  • The Rust FFI Omnibus
- http://jakegoulding.com/rust-ffi-omnibus/ - FFIのパターン集

  • Variance in Rust
- https://doc.rust-lang.org/nomicon/subtyping.html - 変性の形式的説明

  • Oxidizing Firefox
- Mozilla Research Blog - 実践的なRust導入事例