課題3: 自己参照構造体とPin
マンダトリー要件(80点)
問題1: 自己参照構造体の理解(20点)
1.1 問題の本質(8点)
以下の質問に答えなさい:
- 自己参照構造体がRustで困難な理由を、ムーブセマンティクスの観点から説明しなさい。(3点)
- 以下のコードがコンパイルエラーになる理由をメモリレイアウトの図を使って説明しなさい:
struct SelfRef<'a> {
data: String,
pointer: &'a str,
}
impl<'a> SelfRef<'a> {
fn new(text: String) -> Self {
SelfRef {
data: text,
pointer: &text, // エラー!
}
}
}
(5点)
1.2 解決策の比較(12点)
3つの解決策を比較分析しなさい:
各方法について:
- 実装の複雑さ
- 実行時コスト
- 安全性の保証
を説明しなさい。(各4点)
---
問題2: Pinの実装(25点)
2.1 基本的なPinned構造体(15点)
以下の仕様に従って、Pinned構造体を実装しなさい:
use std::pin::Pin;
use std::marker::PhantomPinned;
/// 文字列データとそのスライスへの参照を持つ構造体
struct StringSlice {
data: String,
slice: *const str,
_pin: PhantomPinned,
}
impl StringSlice {
/// 新しいStringSliceを作成(Pinnedされた状態で返す)
fn new(s: String, start: usize, end: usize) -> Pin<Box<Self>> {
// TODO: 実装
todo!()
}
/// データ全体を取得
fn get_data(self: Pin<&Self>) -> &str {
// TODO: 実装
todo!()
}
/// スライス部分を取得
fn get_slice(self: Pin<&Self>) -> &str {
// TODO: 実装(unsafeブロックが必要)
todo!()
}
}
要件:
new()メソッドで正しく自己参照を設定すること(5点)get_data()とget_slice()を安全に実装すること(5点)- テストコードを書いて動作を確認すること(5点)
テストケース:
#[test]
fn test_string_slice() {
let s = StringSlice::new("Hello, World!".to_string(), 0, 5);
s.as_ref().with(|s| {
assert_eq!(s.get_data(), "Hello, World!");
assert_eq!(s.get_slice(), "Hello");
});
}
2.2 Pinned構造体の操作(10点)
StringSliceに以下のメソッドを追加実装しなさい:
impl StringSlice {
/// スライスの範囲を更新(新しいPinnedインスタンスを返す)
fn update_slice(
self: Pin<&mut Self>,
start: usize,
end: usize,
) -> Result<(), &'static str> {
// TODO: 実装
// unsafeブロック内で slice ポインタを更新
todo!()
}
/// データの長さを取得
fn data_len(self: Pin<&Self>) -> usize {
// TODO: 実装
todo!()
}
/// スライスの長さを取得
fn slice_len(self: Pin<&Self>) -> usize {
// TODO: 実装
todo!()
}
}
要件:
update_slice()で範囲チェックを実装すること(4点)- 各メソッドが正しく動作すること(3点)
- テストを作成すること(3点)
---
問題3: Unpin vs !Unpin(20点)
3.1 Unpinな構造体(10点)
以下のUnpinな構造体を実装しなさい:
struct Movable {
x: i32,
y: String,
}
impl Movable {
fn new(x: i32, y: String) -> Self {
Movable { x, y }
}
fn into_pinned(self) -> Pin<Box<Self>> {
Box::pin(self)
}
fn from_pinned(pinned: Pin<Box<Self>>) -> Self {
// TODO: Pin::into_inner()を使って実装
todo!()
}
}
#[test]
fn test_movable() {
let m1 = Movable::new(42, "test".to_string());
let pinned = m1.into_pinned();
// Unpinなのでムーブできる
let m2 = Movable::from_pinned(pinned);
assert_eq!(m2.x, 42);
assert_eq!(m2.y, "test");
}
要件:
from_pinned()を正しく実装(5点)- なぜこれが安全か説明すること(5点)
3.2 !Unpinな構造体の制限(10点)
以下のコードを分析し、質問に答えなさい:
use std::marker::PhantomPinned;
struct NotMovable {
data: String,
ptr: *const String,
_pin: PhantomPinned,
}
fn attempt_move(pinned: Pin<Box<NotMovable>>) {
// 以下のコードはコンパイルエラーになる
// let moved = Pin::into_inner(pinned);
}
Pin::into_inner()が使えないか説明しなさい。(3点)PhantomPinnedを削除するとどうなるか説明しなさい。(3点)!Unpinを保ちつつデータにアクセスする方法を示しなさい。(4点)---
問題4: Future Traitとライフタイム(15点)
4.1 SimpleFutureの実装(15点)
以下の簡単なFutureを実装しなさい:
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
/// カウンターFuture: 指定回数poll()されたらReady
struct CounterFuture {
count: u32,
target: u32,
}
impl CounterFuture {
fn new(target: u32) -> Self {
CounterFuture { count: 0, target }
}
}
impl Future for CounterFuture {
type Output = u32;
fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
// TODO: 実装
// count をインクリメントし、target に達したら Poll::Ready(count)
// そうでなければ Poll::Pending
todo!()
}
}
#[tokio::test]
async fn test_counter_future() {
let result = CounterFuture::new(3).await;
assert_eq!(result, 3);
}
要件:
poll()を正しく実装すること(5点)- なぜ
self: Pin<&mut Self>が必要か説明すること(5点) get_unchecked_mut()を使ってcountを更新すること(5点)
ヒント:
impl Future for CounterFuture {
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
unsafe {
let this = self.as_mut().get_unchecked_mut();
this.count += 1;
// ...
}
}
}
---
ボーナス要件(20点)
ボーナス1: ouroboros風のマクロ実装(10点)
簡易版の自己参照構造体マクロを実装しなさい:
// 使用例
simple_self_ref! {
struct MyStruct {
data: String,
slice: &'this str, // data を参照
}
}
// 展開後のイメージ:
// - MyStructBuilder を生成
// - with_slice() メソッドを提供
// - Pin<Box<MyStruct>> を返す
実装要件:
proc_macroではなく、macro_rules!で実装(5点)- 基本的な自己参照のセットアップを自動化(3点)
- サンプルコードで動作を確認(2点)
ヒント:
macro_rules! simple_self_ref {
(
struct $name:ident {
$data_field:ident: $data_type:ty,
$ref_field:ident: &'this $ref_type:ty,
}
) => {
struct $name {
$data_field: $data_type,
$ref_field: *const $ref_type,
_pin: std::marker::PhantomPinned,
}
// Builder や with_ メソッドを生成
// ...
};
}
---
ボーナス2: 高度な自己参照パターン(10点)
複数の自己参照を持つ構造体を実装しなさい:
use std::pin::Pin;
use std::marker::PhantomPinned;
/// テキストデータと2つの異なるスライスを持つ
struct MultiSlice {
text: String,
// 前半部分への参照
first_half: *const str,
// 後半部分への参照
second_half: *const str,
_pin: PhantomPinned,
}
impl MultiSlice {
fn new(text: String) -> Pin<Box<Self>> {
// TODO: 実装
// text を2つに分割して、それぞれのポインタを設定
todo!()
}
fn get_text(self: Pin<&Self>) -> &str {
// TODO: 実装
todo!()
}
fn get_first_half(self: Pin<&Self>) -> &str {
// TODO: 実装
todo!()
}
fn get_second_half(self: Pin<&Self>) -> &str {
// TODO: 実装
todo!()
}
fn swap_halves(self: Pin<&mut Self>) {
// TODO: 実装
// first_half と second_half のポインタを入れ替える
todo!()
}
}
#[test]
fn test_multi_slice() {
let mut ms = MultiSlice::new("HelloWorld".to_string());
assert_eq!(ms.as_ref().get_first_half(), "Hello");
assert_eq!(ms.as_ref().get_second_half(), "World");
ms.as_mut().swap_halves();
assert_eq!(ms.as_ref().get_first_half(), "World");
assert_eq!(ms.as_ref().get_second_half(), "Hello");
}
要件:
- 複数のポインタを正しく管理(4点)
swap_halves()の安全な実装(3点)- すべてのテストをパス(3点)
- コードファイル:
---
提出要件
src/lib.rs または src/main.rs
- すべての問題の実装を含む
- コンパイルが通ること- テストファイル:
tests/ディレクトリ内に統合テスト
- すべてのテストがパスすること- ドキュメント:
ANSWERS.mdに各問題の説明を記述
- 特にunsafeを使う理由を明記- Cargo.toml:
[package]
name = "pin-exercise"
version = "0.1.0"
edition = "2021"
[dependencies]
# ボーナス問題で必要な場合のみ
[dev-dependencies]
tokio = { version = "1", features = ["full"] }
---
評価基準
マンダトリー(80点)
- 問題1(20点): 概念の理解度
- 問題2(25点): Pin
の実装力 - 問題3(20点): Unpin/!Unpinの理解
- 問題4(15点): Future実装の正確性
ボーナス(20点)
- ボーナス1(10点): マクロ実装のクリエイティビティ
- ボーナス2(10点): 複雑な自己参照の処理
コード品質
- unsafeの適切な使用
- エラーハンドリング
- ドキュメンテーション
---
ヒントとリソース
Pinの重要なメソッド
// 安全な操作(Unpin型のみ)
Pin::new(pointer)
Pin::into_inner(pinned)
// unsafe操作(すべての型)
Pin::new_unchecked(pointer)
Pin::get_unchecked_mut(pinned)
Pin::map_unchecked_mut(pinned, |x| &mut x.field)
デバッグのコツ
Pin<&mut T>"
- "the trait Unpin is not implemented"- メモリレイアウトを図示:
Stack: Heap:
┌──────────┐ ┌─────────────┐
│ Pin<Box> │──────►│ Actual Data │
└──────────┘ └─────────────┘
- unsafe使用時のチェックリスト:
参考リンク
---
よくある質問
*Q1: なぜ生ポインタ(const T)を使うのか?
A1: 参照(&T)はライフタイムが必要だが、自己参照の場合、ライフタイムを表現できない。生ポインタはライフタイムを持たないため、この問題を回避できる。
Q2: Pin::get_unchecked_mut()はいつ安全か?
A2: 以下の条件を満たす場合:
Unpinである
Q3: PhantomPinnedは実行時コストがあるか?**
A3: いいえ。ゼロサイズ型なので、コンパイル時の型チェックにのみ使われる。
---
頑張ってください!Pin