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 注意点
---
練習問題
問題1
PhantomDataを使って、型パラメータを持つが実際にはデータを保持しない構造体を実装しなさい。問題2
Pinを使った自己参照構造体を実装し、なぜPinが必要なのか説明しなさい。---
コース完了
Rust Ownership Deep Dive コース完了おめでとうございます!
次のステップ:
- Rust Lifetime Mastery コースへ進む
- unsafeコードを含む実践的なライブラリを作成
- オープンソースプロジェクトのunsafeコードをレビュー