Chapter 4: 上級者向けテクニック

学習目標

  • unsafeコードにおける所有権の扱いを理解する
  • PhantomDataの使い方を習得する
  • ドロップチェッカーとドロップフラグを理解する
  • カスタムアロケータの実装方法を学ぶ
  • 所有権システムの限界とその対処法を理解する

---

4.1 unsafe と所有権

4.1.1 unsafeの5つの超能力

// 1. 生ポインタのデリファレンス
let mut num = 5;
let r1 = &num as *const i32;
let r2 = &mut num as *mut i32;

unsafe {
    println!("r1: {}", *r1);
    *r2 = 10;
}

// 2. unsafe関数の呼び出し
unsafe fn dangerous() {}
unsafe {
    dangerous();
}

// 3. unsafe traitの実装
unsafe trait Foo {}
unsafe impl Foo for i32 {}

// 4. static mutへのアクセス
static mut COUNTER: u32 = 0;
unsafe {
    COUNTER += 1;
}

// 5. union fieldへのアクセス
union MyUnion {
    f1: u32,
    f2: f32,
}

4.1.2 所有権不変条件の維持

use std::slice;

fn split_at_mut(slice: &mut [i32], mid: usize) -> (&mut [i32], &mut [i32]) {
    let len = slice.len();
    let ptr = slice.as_mut_ptr();

    assert!(mid <= len);

    unsafe {
        (
            slice::from_raw_parts_mut(ptr, mid),
            slice::from_raw_parts_mut(ptr.add(mid), len - mid),
        )
    }
}

重要な原則

  • unsafeブロック内でも所有権の不変条件を守る
  • メモリの重複アクセスを避ける
  • ライフタイムを正しく扱う

---

4.2 PhantomData

4.2.1 ゴーストタイプパラメータ

use std::marker::PhantomData;

struct Slice<'a, T> {
    ptr: *const T,
    len: usize,
    _marker: PhantomData<&'a T>,
    //       ^^^^^^^^^^^^^^^^^
    //       実際にはデータを持たない
    //       コンパイラに型情報を伝える
}

impl<'a, T> Slice<'a, T> {
    fn new(slice: &'a [T]) -> Self {
        Slice {
            ptr: slice.as_ptr(),
            len: slice.len(),
            _marker: PhantomData,
        }
    }

    unsafe fn get(&self, index: usize) -> Option<&'a T> {
        if index < self.len {
            Some(&*self.ptr.add(index))
        } else {
            None
        }
    }
}

4.2.2 Drop Check

use std::marker::PhantomData;

struct Inspector<'a, T>(&'a T);

impl<'a, T> Drop for Inspector<'a, T> {
    fn drop(&mut self) {
        // Tのメソッドを呼び出す可能性
        // println!("{:?}", self.0);
    }
}

// PhantomData で所有権関係を明示
struct SafeInspector<'a, T> {
    _data: PhantomData<&'a T>,
}

---

4.3 ドロップチェッカー

4.3.1 ドロップ順序

struct Outer<T>(T);

impl<T> Drop for Outer<T> {
    fn drop(&mut self) {
        println!("Dropping Outer");
    }
}

struct Inner;

impl Drop for Inner {
    fn drop(&mut self) {
        println!("Dropping Inner");
    }
}

fn main() {
    let _x = Outer(Inner);
}
// 出力:
// Dropping Outer
// Dropping Inner

ドロップ順序の原則

  • 外側から内側へ
  • フィールドは宣言順
  • タプル/配列は先頭から
  • 4.3.2 #[may_dangle]

    #![feature(dropck_eyepatch)]
    
    use std::marker::PhantomData;
    
    unsafe impl<#[may_dangle] T> Drop for Vec<T> {
        fn drop(&mut self) {
            // Tを使わないのでmay_dangleが安全
            unsafe {
                let ptr = self.as_mut_ptr();
                let cap = self.capacity();
                std::ptr::drop_in_place(
                    std::slice::from_raw_parts_mut(ptr, self.len())
                );
                std::alloc::dealloc(
                    ptr as *mut u8,
                    std::alloc::Layout::array::<T>(cap).unwrap(),
                );
            }
        }
    }
    

    ---

    4.4 カスタムアロケータ

    4.4.1 Allocator トレイト

    use std::alloc::{Allocator, Global, Layout};
    use std::ptr::NonNull;
    
    struct MyAllocator;
    
    unsafe impl Allocator for MyAllocator {
        fn allocate(&self, layout: Layout) -> Result<NonNull<[u8]>, std::alloc::AllocError> {
            // カスタム割り当てロジック
            Global.allocate(layout)
        }
    
        unsafe fn deallocate(&self, ptr: NonNull<u8>, layout: Layout) {
            // カスタム解放ロジック
            Global.deallocate(ptr, layout)
        }
    }
    

    4.4.2 Box with custom allocator

    use std::alloc::Allocator;
    
    fn custom_box<T, A: Allocator>(value: T, alloc: A) -> Box<T, A> {
        Box::new_in(value, alloc)
    }
    

    ---

    4.5 ピニング(Pinning)

    4.5.1 自己参照構造体

    use std::pin::Pin;
    
    struct SelfReferential {
        data: String,
        pointer: *const String,  // dataを指す
    }
    
    impl SelfReferential {
        fn new(data: String) -> Pin<Box<Self>> {
            let mut boxed = Box::pin(SelfReferential {
                data,
                pointer: std::ptr::null(),
            });
    
            let self_ptr: *const String = &boxed.data;
            unsafe {
                let mut_ref: Pin<&mut Self> = Pin::as_mut(&mut boxed);
                Pin::get_unchecked_mut(mut_ref).pointer = self_ptr;
            }
    
            boxed
        }
    
        fn get_data(&self) -> &str {
            &self.data
        }
    
        fn get_pointer_data(&self) -> &str {
            unsafe { &*self.pointer }
        }
    }
    

    4.5.2 Unpin トレイト

    // ほとんどの型はUnpinを実装
    struct Normal {
        data: i32,
    }
    
    // !Unpinが必要な例
    struct MustBePinned {
        data: String,
        ptr: *const String,
        _pin: std::marker::PhantomPinned,
        //    ^^^^^^^^^^^^^^^^^^^^^^^^^^^
        //    Unpinを実装しないマーカー
    }
    

    ---

    4.6 メモリレイアウトの制御

    4.6.1 repr属性

    #[repr(C)]
    struct CCompatible {
        x: i32,
        y: i32,
    }
    
    #[repr(transparent)]
    struct Wrapper(i32);
    
    #[repr(packed)]
    struct Packed {
        a: u8,
        b: u32,  // パディングなし
    }
    
    #[repr(align(16))]
    struct Aligned {
        data: [u8; 16],
    }
    

    4.6.2 サイズとアライメント

    use std::mem::{size_of, align_of};
    
    println!("size: {}", size_of::<Option<&i32>>());      // 8
    println!("size: {}", size_of::<Option<Box<i32>>>());  // 8
    println!("align: {}", align_of::<i64>());             // 8
    

    ---

    4.7 型レベルプログラミング

    4.7.1 型状態パターン(再訪)

    struct Open;
    struct Closed;
    
    struct Door<State> {
        _state: std::marker::PhantomData<State>,
    }
    
    impl Door<Closed> {
        fn open(self) -> Door<Open> {
            Door { _state: std::marker::PhantomData }
        }
    }
    
    impl Door<Open> {
        fn close(self) -> Door<Closed> {
            Door { _state: std::marker::PhantomData }
        }
    
        fn walk_through(&self) {
            println!("通過");
        }
    }
    

    4.7.2 ゼロコスト型安全性

    struct Meters(f64);
    struct Seconds(f64);
    
    struct Velocity(f64);  // m/s
    
    impl std::ops::Div<Seconds> for Meters {
        type Output = Velocity;
    
        fn div(self, rhs: Seconds) -> Velocity {
            Velocity(self.0 / rhs.0)
        }
    }
    
    fn main() {
        let distance = Meters(100.0);
        let time = Seconds(10.0);
        let velocity = distance / time;
    
        // 型安全:異なる単位の混在を防ぐ
        // let wrong = distance / Meters(50.0);  // エラー
    }
    

    ---

    4.8 所有権システムの限界への対処

    4.8.1 Ghostセル

    // 概念的な実装
    use std::cell::Cell;
    
    struct GhostToken<'id> {
        _marker: std::marker::PhantomData<*mut &'id ()>,
    }
    
    struct GhostCell<'id, T> {
        value: Cell<T>,
        _marker: std::marker::PhantomData<&'id ()>,
    }
    
    // 実際の使用にはghost-cell クレートを使用
    

    4.8.2 Rental パターン(廃止済み)

    // 以前は使われていたパターン(現在は非推奨)
    // 代わりにouroboros クレートを使用
    
    use ouroboros::self_referencing;
    
    #[self_referencing]
    struct MyStruct {
        data: String,
        #[borrows(data)]
        data_ref: &'this str,
    }
    

    ---

    4.9 まとめ

    4.9.1 上級テクニックの使い分け

    ┌─────────────────────────────────────────────────────────┐
    │         テクニックの選択ガイド                            │
    ├─────────────────────────────────────────────────────────┤
    │                                                         │
    │  PhantomData:                                           │
    │  - ライフタイム/型パラメータを保持                        │
    │  - Drop checkに影響                                     │
    │                                                         │
    │  Pin:                                                   │
    │  - 自己参照構造体                                        │
    │  - async/await                                          │
    │                                                         │
    │  repr:                                                  │
    │  - FFI                                                  │
    │  - メモリレイアウト制御                                  │
    │                                                         │
    │  unsafe:                                                │
    │  - パフォーマンス最適化                                  │
    │  - ライブラリの内部実装                                  │
    │                                                         │
    └─────────────────────────────────────────────────────────┘
    

    4.9.2 注意点

  • unsafeは最小限に
  • 所有権不変条件を常に維持
  • ドキュメントで安全性を明記
  • テストを徹底的に

---

練習問題

問題1

PhantomDataを使って、型パラメータを持つが実際にはデータを保持しない構造体を実装しなさい。

問題2

Pinを使った自己参照構造体を実装し、なぜPinが必要なのか説明しなさい。

---

コース完了

Rust Ownership Deep Dive コース完了おめでとうございます!

次のステップ:

  • Rust Lifetime Mastery コースへ進む
  • unsafeコードを含む実践的なライブラリを作成
  • オープンソースプロジェクトのunsafeコードをレビュー