Chapter 3: 自己参照構造体の深層理解
この章の目標
この章を読み終えると、以下のことが理解できるようになります:
- 自己参照構造体がRustで困難な理由の本質
- Pin
とUnpinの仕組みと実装 - std::pinモジュールの内部構造
- FutureとAsync/Awaitのライフタイム
- ouroborosとrentalクレートの設計原理
- 非同期プログラミング: Future内部での状態管理
- 高性能パーサー: 入力データへの参照を保持
- ゼロコピーデシリアライゼーション: メモリコピーなしでデータ解析
- イベントループ実装: コールバック関数の管理
なぜ自己参照構造体が重要か
自己参照構造体は、以下のような実世界のユースケースで必要になります:
この章で学ぶPin
---
1. 自己参照構造体の問題
1.1 なぜ自己参照が難しいのか
単純な試み:
struct SelfRef {
data: String,
pointer: &String, // コンパイルエラー!
}
エラーメッセージ:
error[E0106]: missing lifetime specifier
--> src/main.rs:3:14
|
3 | pointer: &String,
| ^ expected named lifetime parameter
ライフタイムを追加しても...:
struct SelfRef<'a> {
data: String,
pointer: &'a String,
}
impl<'a> SelfRef<'a> {
fn new(text: String) -> Self {
let data = text;
SelfRef {
data,
pointer: &data, // エラー!borrowed value does not live long enough
}
}
}
1.2 根本的な問題:ムーブセマンティクス
問題の本質:
初期状態:
┌─────────────────┐
│ SelfRef │
│ ┌─────────────┐ │
│ │ data: "hi" │ │
│ └─────────────┘ │
│ ↑ │
│ ┌──────┴──────┐ │
│ │ pointer │ │
│ └─────────────┘ │
└─────────────────┘
Address: 0x1000
ムーブ後:
┌─────────────────┐
│ SelfRef │
│ ┌─────────────┐ │
│ │ data: "hi" │ │ ← 新しいアドレス!
│ └─────────────┘ │
│ ↑ │
│ ┌──────┼──────┐ │
│ │ pointer: ───┼─┼──► 0x1000 (無効!)
│ └─────────────┘ │
└─────────────────┘
Address: 0x2000
視覚化:
fn demonstrate_problem() {
let mut s1 = SelfRef::new("hello");
// s1をムーブ
let s2 = s1; // ← data のアドレスが変わる!
// でも pointer は古いアドレスを指したまま
// s2.pointer は今やダングリングポインタ!
}
1.3 Rustの所有権モデルとの衝突
Rustの3つの保証:
- ムーブセマンティクス: 値は自由に移動できる
- メモリ安全性: ダングリングポインタは存在しない
- ゼロコスト抽象化: 実行時オーバーヘッドなし
- データの物理アドレスを固定 ← ムーブと矛盾!
- 禁止する: ムーブを不可能にする → Pin
- 更新する: ムーブ時にポインタを修正 → 実行時コスト
- 間接参照: Boxやスマートポインタ → ヒープ割り当て
自己参照の要求:
解決策の方向性:
Rustは1番(Pin
---
2. Pinの仕組み
2.1 Pinの基本定義
std::pinモジュール:
pub struct Pin<P> {
pointer: P,
}
impl<P: Deref> Pin<P> {
pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
Pin { pointer }
}
pub fn as_ref(&self) -> Pin<&P::Target> {
// 重要: Pinを維持したまま参照を取得
unsafe { Pin::new_unchecked(&*self.pointer) }
}
}
Pinの保証:
Pin<P<T>> は以下を保証する:
1. T が !Unpin の場合:
- T をムーブできない
- &mut T を取得できない(ムーブに使えるため)
2. T が Unpin の場合:
- 通常の型として扱える
- Pin<P<T>> ≈ P<T>
2.2 Unpinトレイト
定義:
pub auto trait Unpin {}
auto trait:
- ほとんどの型に自動実装される
- 明示的にオプトアウト可能
Unpinな型(デフォルト):
struct Regular {
x: i32,
y: String,
}
// Regular は自動的に Unpin
fn move_regular(p: Pin<Box<Regular>>) {
let r: Box<Regular> = Pin::into_inner(p); // OK!
}
!Unpinな型(オプトアウト):
use std::marker::PhantomPinned;
struct NotUnpin {
data: String,
pointer: *const String,
_pin: PhantomPinned, // これで !Unpin になる
}
fn cannot_move(p: Pin<Box<NotUnpin>>) {
// let n = Pin::into_inner(p); // コンパイルエラー!
}
2.3 PhantomPinnedの役割
定義:
pub struct PhantomPinned;
impl !Unpin for PhantomPinned {}
使用例:
use std::marker::PhantomPinned;
use std::pin::Pin;
struct SelfRef {
data: String,
// 生ポインタを使う(ライフタイムの問題を回避)
pointer: *const String,
_pin: PhantomPinned,
}
impl SelfRef {
fn new(text: String) -> Pin<Box<Self>> {
let mut boxed = Box::pin(SelfRef {
data: text,
pointer: 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).pointer = self_ptr;
}
boxed
}
fn get_data(self: Pin<&Self>) -> &str {
&self.data
}
fn get_pointer_data(self: Pin<&Self>) -> &str {
unsafe {
// pointer は data を指している
&*self.pointer
}
}
}
メモリレイアウト:
Box<SelfRef> in heap:
┌─────────────────────────┐
│ SelfRef │
│ ┌─────────────────────┐ │
│ │ data: String │ │ ← 0x7fff1000
│ │ ptr: 0xabcd │ │
│ │ len: 5 │ │
│ │ cap: 5 │ │
│ └─────────────────────┘ │
│ ┌─────────────────────┐ │
│ │ pointer: 0x7fff1000│ │ ← data を指す
│ └─────────────────────┘ │
│ ┌─────────────────────┐ │
│ │ _pin: PhantomPinned│ │ ← ゼロサイズ
│ └─────────────────────┘ │
└─────────────────────────┘
Pin<Box<SelfRef>> により、この Box はムーブ不可
---
3. std::pinモジュールの内部構造
3.1 主要なAPI
安全なAPI:
// 1. Pin::new - Unpin型のみ
impl<P: Deref<Target: Unpin>> Pin<P> {
pub fn new(pointer: P) -> Pin<P> {
unsafe { Pin::new_unchecked(pointer) }
}
}
// 2. into_inner - Unpin型のみ
impl<P: Deref<Target: Unpin>> Pin<P> {
pub fn into_inner(pin: Pin<P>) -> P {
pin.pointer
}
}
// 3. get_mut - Unpin型のみ
impl<P: DerefMut<Target: Unpin>> Pin<P> {
pub fn get_mut(self: Pin<&mut P>) -> &mut P::Target {
unsafe { Pin::get_unchecked_mut(self) }
}
}
unsafe API:
// 4. new_unchecked - 任意の型
impl<P> Pin<P> {
pub unsafe fn new_unchecked(pointer: P) -> Pin<P> {
Pin { pointer }
}
}
// 5. get_unchecked_mut - !Unpin型でも可
impl<P: DerefMut> Pin<P> {
pub unsafe fn get_unchecked_mut(self: Pin<&mut P>) -> &mut P::Target {
&mut *self.pointer
}
}
// 6. map_unchecked_mut - フィールド投影
impl<P: DerefMut> Pin<P> {
pub unsafe fn map_unchecked_mut<U, F>(
self: Pin<&mut P>,
func: F,
) -> Pin<&mut U>
where
F: FnOnce(&mut P::Target) -> &mut U,
{
let pointer = Pin::get_unchecked_mut(self);
Pin::new_unchecked(func(pointer))
}
}
3.2 構造的Pinning vs 非構造的Pinning
構造的Pinning:
- 構造体がPinnedなら、フィールドもPinned
#[pin]属性で管理(pin-projectクレート)
非構造的Pinning:
- 一部のフィールドのみPinned
- 他のフィールドは自由にムーブ可能
例:
use std::pin::Pin;
use std::marker::PhantomPinned;
struct Mixed {
// 構造的ピン: この参照は固定
pinned_data: String,
self_ref: *const String,
// 非構造的: 自由にムーブ可能
movable_data: Vec<i32>,
_pin: PhantomPinned,
}
impl Mixed {
fn get_movable_mut(self: Pin<&mut Self>) -> &mut Vec<i32> {
// movable_data は Unpin なので安全に取得
unsafe {
&mut self.get_unchecked_mut().movable_data
}
}
}
3.3 pin-projectクレートの原理
手動実装(複雑):
struct MyStruct {
pinned: String,
unpinned: Vec<i32>,
_pin: PhantomPinned,
}
impl MyStruct {
fn project(self: Pin<&mut Self>) -> (Pin<&mut String>, &mut Vec<i32>) {
unsafe {
let this = self.get_unchecked_mut();
(
Pin::new_unchecked(&mut this.pinned),
&mut this.unpinned,
)
}
}
}
pin-project使用(簡潔):
use pin_project::pin_project;
#[pin_project]
struct MyStruct {
#[pin]
pinned: String,
unpinned: Vec<i32>,
}
// 自動生成された project() メソッドを使用
fn use_projection(mut s: Pin<&mut MyStruct>) {
let projected = s.as_mut().project();
// projected.pinned: Pin<&mut String>
// projected.unpinned: &mut Vec<i32>
}
---
4. FutureとAsync/Awaitのライフタイム
4.1 Future Traitの定義
std::future::Future:
pub trait Future {
type Output;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
// ^^^^^^^^^^^^^^^^
// Pin<&mut Self> が必須!
}
pub enum Poll<T> {
Ready(T),
Pending,
}
なぜPinが必要か:
async fn example() {
let x = String::from("hello");
let y = &x; // ← x への参照を保持
other_async().await; // ← ここで中断・再開
println!("{}", y); // ← y が有効でなければならない
}
内部構造(概念):
// async fn は State Machine に変換される
enum ExampleFuture {
Start,
AwaitingOther {
x: String,
y: *const String, // ← 自己参照!
},
Done,
}
impl Future for ExampleFuture {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
// Futureがムーブされたら y がダングリングポインタになる
// だから Pin<&mut Self> が必要
// ...
}
}
4.2 Async関数のライフタイム
基本的なasync関数:
async fn read_file(path: &str) -> String {
// path はこの関数のライフタイム内で有効
tokio::fs::read_to_string(path).await.unwrap()
}
展開後(概念):
fn read_file<'a>(path: &'a str) -> impl Future<Output = String> + 'a {
// ^^^
// Future は 'a より長生きできない
async move {
tokio::fs::read_to_string(path).await.unwrap()
}
}
複雑な例:
async fn process<'a>(
data: &'a [u8],
config: &'a Config,
) -> Result<Output<'a>, Error> {
let mut buffer = Vec::new();
// 非同期処理
let result = parse_async(data).await?;
// 'a を含む結果を返す
Ok(Output {
parsed: result,
original: data, // ← 'a への参照
cfg: config, // ← 'a への参照
})
}
struct Output<'a> {
parsed: ParsedData,
original: &'a [u8],
cfg: &'a Config,
}
4.3 Futureの組み合わせとライフタイム
join!マクロ:
use tokio::join;
async fn parallel_tasks<'a>(
db: &'a Database,
) -> (Result<User, Error>, Result<Posts, Error>) {
join!(
db.get_user(),
db.get_posts(),
)
// 両方のFutureが 'a を共有
}
select!マクロ:
use tokio::select;
async fn race_tasks<'a>(
source1: &'a Source,
source2: &'a Source,
) -> Data {
select! {
data = source1.fetch() => data,
data = source2.fetch() => data,
// どちらか早い方
}
}
ライフタイム図:
┌─────────────────────────────────────┐
│ async fn のライフタイム 'a │
│ │
│ ┌────────────────┐ │
│ │ Future 1 │ │
│ │ holds &'a Data │ │
│ └────────────────┘ │
│ ∧ │
│ │ join!/select! │
│ ∨ │
│ ┌────────────────┐ │
│ │ Future 2 │ │
│ │ holds &'a Data │ │
│ └────────────────┘ │
│ │
└─────────────────────────────────────┘
---
5. ouroborosとrentalクレートの分析
5.1 rentalクレート(非推奨)
コンセプト:
- 自己参照構造体のコード生成
- Rust 1.0時代の解決策
- 現在は非推奨(Pinの登場により)
例:
// rental crate(古い方法)
use rental::rental;
rental! {
pub mod rent_string {
use super::*;
#[rental]
pub struct OwnedStr {
buffer: String,
slice: &'buffer str, // buffer を参照
}
}
}
問題点:
- マクロが複雑
- デバッグが困難
- 内部でunsafeを多用
5.2 ouroborosクレート(現代的)
設計思想:
- Pin
ベース - proc_macroによるコード生成
- 安全性を最大化
基本使用例:
use ouroboros::self_referencing;
#[self_referencing]
struct MyStruct {
data: String,
#[borrows(data)]
#[covariant]
slice: &'this str,
}
fn main() {
let my_struct = MyStructBuilder {
data: "Hello, World!".to_string(),
slice_builder: |data| &data[0..5],
}.build();
my_struct.with_slice(|slice| {
println!("{}", slice); // "Hello"
});
}
メモリレイアウト:
MyStruct(ヒープ上):
┌──────────────────────────┐
│ data: String │
│ ┌──────────────────────┐ │ ← 0x1000
│ │ ptr: heap → "Hello" │ │
│ │ len: 13 │ │
│ │ cap: 13 │ │
│ └──────────────────────┘ │
│ │
│ slice: &str │
│ ┌──────────────────────┐ │
│ │ ptr: 0x1000 (data) │ │ ← data を指す!
│ │ len: 5 │ │
│ └──────────────────────┘ │
└──────────────────────────┘
Pin により、この構造体はムーブ不可
5.3 高度な使用例
複数の自己参照:
#[self_referencing]
struct Parser {
input: String,
#[borrows(input)]
tokens: Vec<&'this str>,
#[borrows(tokens)]
ast: Vec<&'this &'this str>, // tokens 内の要素への参照
}
可変参照:
#[self_referencing]
struct MutableStruct {
data: Vec<i32>,
#[borrows(mut data)]
slice: &'this mut [i32],
}
impl MutableStruct {
fn modify(&mut self) {
self.with_slice_mut(|slice| {
slice[0] = 42;
});
}
}
5.4 ouroboros vs Pin手動実装
ouroboros:
// 簡潔
#[self_referencing]
struct Easy {
data: String,
#[borrows(data)]
slice: &'this str,
}
手動Pin実装:
// 複雑だが柔軟
use std::pin::Pin;
use std::marker::PhantomPinned;
struct Manual {
data: String,
slice: *const str,
_pin: PhantomPinned,
}
impl Manual {
fn new(s: String) -> Pin<Box<Self>> {
let mut b = Box::pin(Manual {
data: s,
slice: std::ptr::null(),
_pin: PhantomPinned,
});
let slice_ptr: *const str = &b.data;
unsafe {
let mut_ref = Pin::as_mut(&mut b);
Pin::get_unchecked_mut(mut_ref).slice = slice_ptr;
}
b
}
fn get_slice(self: Pin<&Self>) -> &str {
unsafe { &*self.slice }
}
}
---
6. 実践的なパターン
6.1 Async Runtimeの内部
Tokio Futureの簡略化されたモデル:
struct JoinHandle<T> {
// Task の内部状態(Pinned Future)
future: Pin<Box<dyn Future<Output = T> + Send>>,
// 完了通知
waker: Arc<Mutex<Option<Waker>>>,
}
impl<T> Future for JoinHandle<T> {
type Output = T;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
// future は Pinned されている
let future = self.future.as_mut();
future.poll(cx)
}
}
6.2 ゼロコピーパーサー
設計:
use ouroboros::self_referencing;
#[self_referencing]
struct ZeroCopyParser {
// 入力データ(所有)
input: Vec<u8>,
// 入力データへの参照(パース結果)
#[borrows(input)]
parsed: ParsedData<'this>,
}
struct ParsedData<'a> {
header: &'a [u8],
body: &'a [u8],
footer: &'a [u8],
}
impl ZeroCopyParser {
fn parse(data: Vec<u8>) -> Self {
ZeroCopyParserBuilder {
input: data,
parsed_builder: |input| {
// ゼロコピーでパース
let header_end = find_header_end(input);
let body_end = find_body_end(input);
ParsedData {
header: &input[0..header_end],
body: &input[header_end..body_end],
footer: &input[body_end..],
}
},
}.build()
}
}
fn find_header_end(data: &[u8]) -> usize {
// 実装省略
0
}
fn find_body_end(data: &[u8]) -> usize {
// 実装省略
data.len()
}
6.3 イベント駆動システム
コールバックとライフタイム:
use std::pin::Pin;
use std::marker::PhantomPinned;
struct EventLoop {
events: Vec<Event>,
// 自己参照: callbacks は events を参照
callbacks: Vec<*const Event>,
_pin: PhantomPinned,
}
struct Event {
id: u64,
data: String,
}
impl EventLoop {
fn new() -> Pin<Box<Self>> {
Box::pin(EventLoop {
events: Vec::new(),
callbacks: Vec::new(),
_pin: PhantomPinned,
})
}
fn add_event(mut self: Pin<&mut Self>, event: Event) {
unsafe {
let this = self.as_mut().get_unchecked_mut();
this.events.push(event);
// 最後のイベントへのポインタ
let event_ptr = this.events.last().unwrap() as *const Event;
this.callbacks.push(event_ptr);
}
}
fn process(self: Pin<&Self>) {
for callback_ptr in &self.callbacks {
unsafe {
let event = &**callback_ptr;
println!("Processing event {}: {}", event.id, event.data);
}
}
}
}
---
まとめ
重要なポイント
- 自己参照構造体の本質:
- Pin
の仕組み :
- Async/Await:
- 実用的なツール:
次のステップ
次章では、以下を学習します:
- Generic Associated Types(GATs)
- Lending Iterators
- Streaming Iterators
- 高度なAsync Lifetimeパターン
自己参照構造体の理解は、Rustの最も高度なメモリ安全性機構を理解する鍵です。