Chapter 3: 自己参照構造体

学習目標

  • 自己参照構造体の問題と解決策を理解する
  • Pin と Unpin の詳細を習得する
  • async/await における Pin の役割を学ぶ
  • rental パターン(非推奨)と ouroboros を理解する
  • 実践的な自己参照構造体の実装方法を学ぶ

---

3.1 自己参照構造体の問題

3.1.1 なぜ難しいのか

// 理想的なコード(動かない)
struct SelfRef {
    data: String,
    ptr: &String,  // コンパイルエラー!
    //   ^^^^^^
    //   ライフタイムが必要だが、どう指定する?
}

// ライフタイムを付けても...
struct SelfRef<'a> {
    data: String,
    ptr: &'a String,  // これは何を参照?
}

根本的な問題

所有権システムの前提:
- 値がムーブすると、古いアドレスは無効
- 参照は有効なアドレスを指し続ける必要

自己参照:
- 内部の参照が同じ構造体内を指す
- 構造体がムーブすると参照が無効に

→ Rust の所有権モデルと矛盾!

3.1.2 具体的な問題例

struct Broken {
    data: String,
    ptr: *const String,
}

fn main() {
    let mut broken = Broken {
        data: String::from("hello"),
        ptr: std::ptr::null(),
    };

    // ポインタを設定
    broken.ptr = &broken.data as *const String;

    println!("Before move: {:p}", broken.ptr);

    // ムーブ!
    let broken2 = broken;

    // ポインタは古いアドレスを指したまま
    println!("After move: {:p}", broken2.ptr);
    println!("Actual address: {:p}", &broken2.data);

    // unsafe だが、データは無効!
    // unsafe {
    //     println!("{}", *broken2.ptr);  // 未定義動作
    // }
}

---

3.2 Pin の導入

3.2.1 Pin の基本概念

Pin の保証

Pin<P<T>> が存在する場合、
T はメモリ上で固定され、ムーブできない。

ただし T: Unpin の場合は例外

型定義

pub struct Pin<P> {
    pointer: P,
}

// P: Deref<Target = T> が必要

3.2.2 Unpin トレイト

// ほとんどの型は自動的に Unpin を実装
pub auto trait Unpin {}

// Unpin を無効化するマーカー
pub struct PhantomPinned;

impl !Unpin for PhantomPinned {}

Unpin の意味

T: Unpin の場合:
- Pin<P<T>> から &mut T を安全に取得可能
- 実質的に Pin の制約なし

T: !Unpin の場合:
- unsafe なしで &mut T は取得不可
- 本当にピン留めされている

3.2.3 Pin の使用例

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

struct SelfRef {
    data: String,
    ptr: *const String,
    _pin: PhantomPinned,  // Unpin を無効化
}

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

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

        boxed
    }

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

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

fn main() {
    let s = SelfRef::new(String::from("Hello, Pin!"));

    println!("data: {}", s.as_ref().get_data());
    println!("ptr data: {}", s.as_ref().get_ptr_data());

    // s はピン留めされているのでムーブ不可
    // let s2 = s;  // エラー!
}

---

3.3 Pin API の詳細

3.3.1 安全な操作

use std::pin::Pin;

// Pin の作成
let boxed = Box::pin(42);

// 不変参照の取得(常に安全)
let pinned_ref: Pin<&i32> = boxed.as_ref();

// T: Unpin なら可変参照も取得可能
let mut boxed = Box::pin(42);
let pinned_mut: Pin<&mut i32> = boxed.as_mut();

// Unpin の型は Pin から取り出せる
let value: &mut i32 = Pin::get_mut(pinned_mut);

3.3.2 unsafe な操作

use std::pin::Pin;

let mut boxed = Box::pin(42);
let mut pinned = Pin::as_mut(&mut boxed);

// unsafe: T: !Unpin でも &mut T を取得
unsafe {
    let value: &mut i32 = Pin::get_unchecked_mut(pinned);
    *value = 100;
}

// 責任はプログラマにある:
// - メモリアドレスが変わるような操作をしない
// - 自己参照を壊さない

---

3.4 async/await と Pin

3.4.1 Future と自己参照

async fn example() {
    let data = String::from("hello");
    let future = async {
        // data への参照をキャプチャ
        println!("{}", data);
    };
    future.await;
}

// コンパイラが生成する構造体(概念的)
struct ExampleFuture {
    state: State,
    data: String,
    data_ref: *const String,  // 自己参照!
}

3.4.2 なぜ Future は Pin を要求するか

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

pub trait Future {
    type Output;

    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
    //      ^^^^^^^^^^^^^^^^^
    //      Pin が必要!
}

理由

1. async ブロックは自己参照構造体を生成する可能性
2. Future がムーブされると参照が無効に
3. Pin でムーブを防ぎ、安全性を保証

3.4.3 実践例

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

struct MyFuture {
    data: String,
    ptr: *const String,
}

impl Future for MyFuture {
    type Output = String;

    fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
        // self はピン留めされているので安全
        Poll::Ready(unsafe { (*self.ptr).clone() })
    }
}

---

3.5 ouroboros クレート

3.5.1 基本的な使用

use ouroboros::self_referencing;

#[self_referencing]
struct MyStruct {
    data: String,

    #[borrows(data)]
    data_ref: &'this str,
}

fn main() {
    let my_struct = MyStructBuilder {
        data: "Hello, ouroboros!".to_string(),
        data_ref_builder: |data: &String| data.as_str(),
    }.build();

    my_struct.with_data_ref(|data_ref| {
        println!("data_ref: {}", data_ref);
    });
}

3.5.2 複雑な例

use ouroboros::self_referencing;

#[self_referencing]
struct CachedParser {
    source: String,

    #[borrows(source)]
    tokens: Vec<&'this str>,

    #[borrows(tokens)]
    ast: Vec<&'this &'this str>,
}

impl CachedParser {
    fn new(source: String) -> CachedParser {
        CachedParserBuilder {
            source,
            tokens_builder: |s| s.split_whitespace().collect(),
            ast_builder: |tokens| {
                tokens.iter().filter(|t| t.len() > 3).collect()
            },
        }.build()
    }
}

---

3.6 rental パターン(非推奨)

3.6.1 歴史的背景

// rental は古いクレート(メンテナンス終了)
// 参考のみ

// rental!{
//     mod rentals {
//         use super::*;
//
//         #[rental]
//         pub struct OwnedStr {
//             buffer: String,
//             suffix: &'buffer str,
//         }
//     }
// }

rental の問題点

  • マクロが複雑
  • エラーメッセージがわかりにくい
  • メンテナンスされていない

代替

  • ouroboros(推奨)
  • 自前で Pin を使う
  • ---

    3.7 実践パターン

    3.7.1 パーサーでの自己参照

    use std::pin::Pin;
    use std::marker::PhantomPinned;
    
    struct Parser {
        source: String,
        current: *const u8,
        _pin: PhantomPinned,
    }
    
    impl Parser {
        fn new(source: String) -> Pin<Box<Self>> {
            let mut parser = Box::pin(Parser {
                source,
                current: std::ptr::null(),
                _pin: PhantomPinned,
            });
    
            let ptr = parser.source.as_ptr();
            unsafe {
                Pin::get_unchecked_mut(Pin::as_mut(&mut parser)).current = ptr;
            }
    
            parser
        }
    
        fn parse(self: Pin<&mut Self>) -> Result<(), &'static str> {
            // current は常に source 内を指す
            Ok(())
        }
    }
    

    3.7.2 イテレータでの自己参照

    use ouroboros::self_referencing;
    
    #[self_referencing]
    struct LendingIterator {
        data: Vec<i32>,
    
        #[borrows(data)]
        #[not_covariant]
        iter: std::slice::Iter<'this, i32>,
    }
    
    impl LendingIterator {
        fn new(data: Vec<i32>) -> Self {
            LendingIteratorBuilder {
                data,
                iter_builder: |data| data.iter(),
            }.build()
        }
    }
    

    ---

    3.8 Pin の設計パターン

    3.8.1 ピン留めされた初期化

    use std::pin::Pin;
    use std::marker::PhantomPinned;
    
    struct InitOnPin {
        value: Option<String>,
        value_ref: Option<*const String>,
        _pin: PhantomPinned,
    }
    
    impl InitOnPin {
        fn new() -> Self {
            InitOnPin {
                value: None,
                value_ref: None,
                _pin: PhantomPinned,
            }
        }
    
        fn init(self: Pin<&mut Self>, value: String) {
            unsafe {
                let this = Pin::get_unchecked_mut(self);
                this.value = Some(value);
                this.value_ref = this.value.as_ref().map(|v| v as *const String);
            }
        }
    }
    

    3.8.2 プロジェクション

    use std::pin::Pin;
    
    struct Container {
        pinned_field: PhantomPinned,
        normal_field: i32,
    }
    
    impl Container {
        // 安全なプロジェクション
        fn project_normal(self: Pin<&mut Self>) -> &mut i32 {
            unsafe { &mut Pin::get_unchecked_mut(self).normal_field }
        }
    
        // ピン留めされたフィールドのプロジェクション
        fn project_pinned(self: Pin<&mut Self>) -> Pin<&mut PhantomPinned> {
            unsafe {
                Pin::map_unchecked_mut(self, |s| &mut s.pinned_field)
            }
        }
    }
    

    ---

    3.9 まとめ

    3.9.1 自己参照構造体の選択肢

    1. 避けられるなら避ける
       - 設計を見直す
       - インデックスを使う
    
    2. Pin<Box<T>>
       - 手動で管理
       - 完全な制御
    
    3. ouroboros
       - マクロで簡単
       - 推奨される方法
    
    4. Pin + unsafe
       - 最大の柔軟性
       - 慎重に使う
    

    3.9.2 重要なポイント

  • 自己参照は Rust の所有権モデルと相性が悪い
  • Pin で安全に扱える
  • async/await で必須の知識
  • ouroboros で簡単に実装可能

---

練習問題

問題1

Pin を使った自己参照構造体を実装し、その安全性を説明しなさい。

問題2

なぜ Future::poll が Pin<&mut Self> を要求するのか説明しなさい。

問題3

ouroboros を使ってパーサーを実装しなさい。

---

次の章へ

Chapter 4: 高度なパターンへ →