課題4: 上級所有権テクニック

マンダトリー要件(80点)

問題1: Unsafe Rustの実装(25点)

1.1 生ポインタの操作(10点)

以下の関数を実装しなさい:

/// 配列の要素を安全に交換する
///
/// # Safety
/// - `arr`は有効なメモリを指していること
/// - `len`は配列の実際の長さであること
/// - `i`と`j`は`len`未満であること
unsafe fn swap_elements(arr: *mut i32, len: usize, i: usize, j: usize) {
    // ここを実装(5点)
}

fn main() {
    let mut data = vec![1, 2, 3, 4, 5];
    let ptr = data.as_mut_ptr();
    let len = data.len();

    unsafe {
        swap_elements(ptr, len, 0, 4);
    }

    assert_eq!(data, vec![5, 2, 3, 4, 1]);
}

評価ポイント

  • 境界チェックの実装(2点)
  • 生ポインタの正しい操作(2点)
  • Safety契約の順守(1点)

追加課題(5点):

  • 安全なラッパー関数safe_swapを実装しなさい
  • safe_swapからunsafe関数を呼び出す
  • 境界チェックを行い、エラーを返す

pub fn safe_swap(arr: &mut [i32], i: usize, j: usize) -> Result<(), &'static str> {
    // ここを実装
}

1.2 Vecの簡易実装(15点)

Vecの簡易版を実装しなさい:

use std::alloc::{alloc, dealloc, realloc, Layout};
use std::ptr;

pub struct SimpleVec<T> {
    ptr: *mut T,
    len: usize,
    capacity: usize,
}

impl<T> SimpleVec<T> {
    pub fn new() -> Self {
        // ここを実装(2点)
    }

    pub fn push(&mut self, value: T) {
        // ここを実装(5点)
        // キャパシティが足りなければ再アロケーション
    }

    pub fn pop(&mut self) -> Option<T> {
        // ここを実装(3点)
    }

    pub fn len(&self) -> usize {
        self.len
    }

    pub fn capacity(&self) -> usize {
        self.capacity
    }
}

impl<T> Drop for SimpleVec<T> {
    fn drop(&mut self) {
        // ここを実装(3点)
        // メモリリークを防ぐ
        // 各要素を適切にドロップ
    }
}

// 安全性のテスト(2点)
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_simple_vec() {
        let mut v = SimpleVec::new();
        v.push(1);
        v.push(2);
        v.push(3);

        assert_eq!(v.len(), 3);
        assert_eq!(v.pop(), Some(3));
        assert_eq!(v.len(), 2);
    }
}

---

問題2: FFI(Foreign Function Interface)(25点)

2.1 C関数のラッパー(15点)

以下のC関数のRustラッパーを実装しなさい:

Cコード

// mylib.h
typedef struct {
    int x;
    int y;
} Point;

Point* point_new(int x, int y);
void point_free(Point* p);
void point_translate(Point* p, int dx, int dy);
int point_distance_squared(const Point* p);

Rustコード

use std::os::raw::c_int;

#[repr(C)]
struct Point {
    x: c_int,
    y: c_int,
}

extern "C" {
    fn point_new(x: c_int, y: c_int) -> *mut Point;
    fn point_free(p: *mut Point);
    fn point_translate(p: *mut Point, dx: c_int, dy: c_int);
    fn point_distance_squared(p: *const Point) -> c_int;
}

/// Rustの安全なラッパー
pub struct RustPoint {
    ptr: *mut Point,
}

impl RustPoint {
    pub fn new(x: i32, y: i32) -> Self {
        // ここを実装(3点)
        // C関数を呼び出す
    }

    pub fn translate(&mut self, dx: i32, dy: i32) {
        // ここを実装(3点)
    }

    pub fn distance_squared(&self) -> i32 {
        // ここを実装(3点)
    }

    pub fn coords(&self) -> (i32, i32) {
        // ここを実装(2点)
        // ポインタから値を読み取る
    }
}

impl Drop for RustPoint {
    fn drop(&mut self) {
        // ここを実装(2点)
        // C側のメモリを解放
    }
}

// Send/Syncの実装判断(2点)
// 実装すべきか?その理由は?

2.2 文字列のFFI(10点)

以下のC関数とやり取りする関数を実装しなさい:

// C側
char* process_string(const char* input);
void free_c_string(char* s);

use std::ffi::{CString, CStr};
use std::os::raw::c_char;

extern "C" {
    fn process_string(input: *const c_char) -> *mut c_char;
    fn free_c_string(s: *mut c_char);
}

/// RustのStringをC関数で処理して返す
pub fn rust_process_string(input: &str) -> Result<String, Box<dyn std::error::Error>> {
    // ここを実装(10点)
    // 1. Rust &str → CString(2点)
    // 2. C関数の呼び出し(2点)
    // 3. C char* → Rust String(3点)
    // 4. C側メモリの解放(2点)
    // 5. エラーハンドリング(1点)
}

---

問題3: Pinと自己参照構造体(20点)

3.1 自己参照構造体の実装(15点)

以下の仕様を満たす自己参照構造体を実装しなさい:

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

/// データとそのスライスへの参照を持つ構造体
struct SelfRefSlice {
    data: Vec<u8>,
    slice_ptr: *const [u8],  // dataの一部を指す
    _pin: PhantomPinned,
}

impl SelfRefSlice {
    /// 新しいインスタンスを作成
    /// start..endの範囲をslice_ptrで指す
    pub fn new(data: Vec<u8>, start: usize, end: usize) -> Pin<Box<Self>> {
        // ここを実装(7点)
        // 1. 構造体を作成(2点)
        // 2. ヒープに確保(2点)
        // 3. slice_ptrを正しく設定(2点)
        // 4. Pinで固定(1点)
    }

    /// dataの内容を取得
    pub fn get_data(self: Pin<&Self>) -> &[u8] {
        // ここを実装(2点)
    }

    /// slice_ptrが指す内容を取得
    pub fn get_slice(self: Pin<&Self>) -> &[u8] {
        // ここを実装(3点)
        // unsafeブロックが必要
    }

    /// slice_ptrの範囲を変更
    pub fn update_slice(self: Pin<&mut Self>, start: usize, end: usize) {
        // ここを実装(3点)
        // unsafeブロックが必要
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_self_ref_slice() {
        let data = vec![1, 2, 3, 4, 5];
        let s = SelfRefSlice::new(data, 1, 4);

        assert_eq!(s.as_ref().get_data(), &[1, 2, 3, 4, 5]);
        assert_eq!(s.as_ref().get_slice(), &[2, 3, 4]);
    }
}

3.2 Pin APIの理解(5点)

以下の質問に答えなさい:

  • Pin::new vs Pin::new_unchecked(2点)
- 違いは何か? - どちらをいつ使うべきか?

  • Unpinトレイト(2点)
- どの型がUnpinを実装しているか? - PhantomPinnedの役割は?

  • Pin<&mut T> vs Pin>(1点)
- どう使い分けるか?

---

問題4: メモリレイアウト(10点)

4.1 repr属性の実装(10点)

以下の構造体について、適切なrepr属性を選択し、理由を説明しなさい:

// ケース1: C言語のネットワークパケット構造体
struct PacketHeader {
    version: u8,
    flags: u8,
    length: u16,
    checksum: u32,
}

// ケース2: ファイルディスクリプタのラッパー
struct FileDescriptor(i32);

// ケース3: メモリ効率を最大化したい構造体
struct CompactData {
    a: u8,
    b: u32,
    c: u8,
}

// ケース4: 共用体
union FloatOrInt {
    f: f32,
    i: u32,
}

各ケースについて:

  • 適切なrepr属性を選択(各1点、計4点)
  • 理由を説明(各1.5点、計6点)

---

ボーナス課題(20点)

ボーナス1: カスタムアロケータの実装(10点)

グローバルアロケータを実装しなさい:

use std::alloc::{GlobalAlloc, Layout};
use std::sync::atomic::{AtomicUsize, Ordering};

/// アロケーション統計を記録するアロケータ
struct CountingAllocator;

static ALLOCATED: AtomicUsize = AtomicUsize::new(0);
static DEALLOCATED: AtomicUsize = AtomicUsize::new(0);

unsafe impl GlobalAlloc for CountingAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        // ここを実装(3点)
        // System allocatorを使用
        // 統計を更新
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        // ここを実装(3点)
        // System allocatorを使用
        // 統計を更新
    }
}

#[global_allocator]
static GLOBAL: CountingAllocator = CountingAllocator;

pub fn get_allocated() -> usize {
    ALLOCATED.load(Ordering::SeqCst)
}

pub fn get_deallocated() -> usize {
    DEALLOCATED.load(Ordering::SeqCst)
}

pub fn get_current_usage() -> usize {
    get_allocated() - get_deallocated()
}

追加要件(4点):

  • アロケーション失敗時の処理
  • スレッドセーフの保証
  • テストコード

---

ボーナス2: FFIでコールバック関数(10点)

C言語のコールバック関数をRustで実装しなさい:

Cコード

// C側
typedef int (*callback_t)(int);

void process_array(int* arr, size_t len, callback_t cb);

Rustコード

use std::os::raw::c_int;

type Callback = extern "C" fn(c_int) -> c_int;

extern "C" {
    fn process_array(arr: *mut c_int, len: usize, cb: Callback);
}

// Rustのクロージャをコールバックに変換
pub fn rust_process_array<F>(arr: &mut [i32], f: F)
where
    F: Fn(i32) -> i32,
{
    // ここを実装(10点)
    // 課題:
    // 1. クロージャをextern "C"関数に変換
    // 2. クロージャのキャプチャを扱う
    // 3. トランポリン関数パターンを使用
}

// 使用例
fn main() {
    let mut data = vec![1, 2, 3, 4, 5];
    let multiplier = 2;

    rust_process_array(&mut data, |x| x * multiplier);

    assert_eq!(data, vec![2, 4, 6, 8, 10]);
}

ヒント: Box::into_rawBox::from_rawを使用

---

ボーナス3: Async Executorの基礎実装(10点)

簡易的な非同期ランタイムを実装しなさい:

use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
use std::sync::{Arc, Mutex};
use std::collections::VecDeque;

type Task = Pin<Box<dyn Future<Output = ()>>>;

struct SimpleExecutor {
    tasks: Arc<Mutex<VecDeque<Task>>>,
}

impl SimpleExecutor {
    fn new() -> Self {
        // ここを実装
    }

    fn spawn(&self, future: impl Future<Output = ()> + 'static) {
        // ここを実装(3点)
    }

    fn run(&self) {
        // ここを実装(7点)
        // 1. タスクキューからタスクを取得
        // 2. Wakerを作成
        // 3. Futureをpoll
        // 4. Pendingなら再度キューに追加
        // 5. Readyなら完了
    }
}

// Wakerの実装
fn create_waker(tasks: Arc<Mutex<VecDeque<Task>>>) -> Waker {
    // ここを実装
}

// テスト用の非同期関数
async fn example_task() {
    println!("Task running");
}

fn main() {
    let executor = SimpleExecutor::new();
    executor.spawn(example_task());
    executor.run();
}

---

評価基準

マンダトリー部分(80点)

項目 配点 評価ポイント
問題1:Unsafe実装 25点 生ポインタの正確な操作、メモリ管理
問題2:FFI 25点 C/Rustの境界処理、安全性の保証
問題3:Pin 20点 自己参照構造体の実装、Pinの理解
問題4:メモリレイアウト 10点 repr属性の適切な選択

ボーナス部分(20点)

項目 配点 評価ポイント
ボーナス1:カスタムアロケータ 10点 GlobalAllocの正確な実装
ボーナス2:FFIコールバック 10点 クロージャのFFI変換
ボーナス3:Async Executor 10点 Future、Waker、Pinの統合

: ボーナスは最大20点まで加算されます。

---

提出方法

ファイル構成

rust-ownership-deep-dive-04/
├── mandatory/
│   ├── src/
│   │   ├── unsafe_ops.rs
│   │   ├── ffi_wrapper.rs
│   │   ├── self_ref.rs
│   │   └── memory_layout.rs
│   ├── Cargo.toml
│   ├── build.rs           # Cライブラリのビルド
│   └── tests/
│       └── integration.rs
├── bonus1-allocator/
│   └── src/lib.rs
├── bonus2-callback/
│   ├── src/
│   ├── c_lib/
│   │   ├── callback.c
│   │   └── callback.h
│   └── build.rs
└── bonus3-executor/
    └── src/lib.rs

提出期限

  • マンダトリー:第4章学習後、2週間以内
  • ボーナス:コース修了時まで

---

ヒント

問題1のヒント(Unsafe)

生ポインタの操作:

unsafe {
    // ポインタのオフセット
    let elem = ptr.add(index);

    // 読み取り
    let value = elem.read();

    // 書き込み
    elem.write(new_value);

    // スワップ
    std::ptr::swap(ptr1, ptr2);
}

メモリアロケーション:

use std::alloc::{alloc, dealloc, Layout};

unsafe {
    let layout = Layout::array::<i32>(10).unwrap();
    let ptr = alloc(layout) as *mut i32;

    // 使用...

    dealloc(ptr as *mut u8, layout);
}

問題2のヒント(FFI)

C関数の呼び出しパターン:

// パターン1: シンプルな関数
extern "C" {
    fn c_function(x: c_int) -> c_int;
}

unsafe {
    let result = c_function(42);
}

// パターン2: ポインタを返す関数
extern "C" {
    fn create_object() -> *mut Object;
    fn destroy_object(obj: *mut Object);
}

pub struct SafeObject {
    ptr: *mut Object,
}

impl Drop for SafeObject {
    fn drop(&mut self) {
        unsafe {
            destroy_object(self.ptr);
        }
    }
}

問題3のヒント(Pin)

自己参照構造体のパターン:

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

struct SelfRef {
    data: String,
    ptr: *const String,
    _pin: PhantomPinned,
}

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

        let data_ptr: *const String = &boxed.data;
        boxed.ptr = data_ptr;

        unsafe { Pin::new_unchecked(boxed) }
    }
}

問題4のヒント(repr)

選択基準:

// C互換性が必要
#[repr(C)]
struct CCompat { }

// 透過的なラッパー
#[repr(transparent)]
struct Wrapper(Inner);

// パックされた構造体
#[repr(packed)]
struct Packed { }

// アライメント指定
#[repr(align(16))]
struct Aligned { }

ボーナス1のヒント(アロケータ)

GlobalAllocの実装:

use std::alloc::{GlobalAlloc, System, Layout};

struct MyAllocator;

unsafe impl GlobalAlloc for MyAllocator {
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        // 実際のアロケーションはSystemに委譲
        System.alloc(layout)
    }

    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
        System.dealloc(ptr, layout)
    }
}

ボーナス2のヒント(コールバック)

トランポリンパターン:

// extern "C"関数(トランポリン)
extern "C" fn trampoline<F>(value: c_int, data: *mut c_void) -> c_int
where
    F: Fn(i32) -> i32,
{
    let closure = unsafe { &*(data as *const F) };
    closure(value as i32) as c_int
}

// 使用例
let closure = |x: i32| x * 2;
let closure_ptr = &closure as *const _ as *mut c_void;

unsafe {
    c_function_with_callback(trampoline::<_>, closure_ptr);
}

---

学習の確認

この課題を通じて、以下を理解できたか確認してください:

  • [ ] Unsafeの5つの超能力
  • [ ] 生ポインタの安全な操作
  • [ ] メモリアロケーションとドロップ
  • [ ] FFIでのC/Rust境界処理
  • [ ] 文字列のFFI変換
  • [ ] Pinと自己参照構造体
  • [ ] メモリレイアウトとrepr属性
  • [ ] カスタムアロケータの実装
  • [ ] 非同期プログラミングの基礎

コースを修了しました!次のステップ:

  • 実際のプロジェクトでの応用
  • async/awaitの深掘り
  • コンパイラの内部実装の学習
  • ---

    参考資料

    公式ドキュメント

  • The Rustonomicon
- https://doc.rust-lang.org/nomicon/ - Unsafe Rustの完全ガイド

  • std::pin
- https://doc.rust-lang.org/std/pin/ - Pinモジュールのドキュメント

  • FFI Guide
- https://doc.rust-lang.org/nomicon/ffi.html - FFIの詳細

追加リソース

  • "Pin, Unpin, and why Rust needs them"
- https://blog.cloudflare.com/pin-and-unpin-in-rust/ - Cloudflareのブログ

  • "Learning Rust With Entirely Too Many Linked Lists"
- https://rust-unofficial.github.io/too-many-lists/ - Unsafeを使った実装例

  • "The Rust Performance Book"
- https://nnethercote.github.io/perf-book/ - パフォーマンス最適化

  • Miri - Rust Interpreter
- https://github.com/rust-lang/miri - Unsafeコードのテストツール

コミュニティリソース

  • Rust Users Forum
- https://users.rust-lang.org/ - Q&A

  • /r/rust
- https://reddit.com/r/rust - 最新情報とディスカッション

  • Rust Async Book
- https://rust-lang.github.io/async-book/ - 非同期プログラミングの公式ガイド