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 重要なポイント
---
練習問題
問題1
Pin を使った自己参照構造体を実装し、その安全性を説明しなさい。問題2
なぜ Future::poll が Pin<&mut Self> を要求するのか説明しなさい。問題3
ouroboros を使ってパーサーを実装しなさい。---