課題18: unsafe Rust

マンダトリー要件

問題1: rawポインタの操作(30点)

rawポインタを使って安全な抽象化を作成しなさい。

// カスタムVecの簡易版を実装
struct SimpleVec<T> {
    ptr: *mut T,
    len: usize,
    capacity: usize,
}

impl<T> SimpleVec<T> {
    fn new() -> Self {
        // TODO: 実装 (5点)
    }

    fn push(&mut self, value: T) {
        // TODO: 実装 (10点)
        // キャパシティが不足したら再割り当て
    }

    fn get(&self, index: usize) -> Option<&T> {
        // TODO: 実装 (5点)
    }

    fn len(&self) -> usize {
        // TODO: 実装 (2点)
    }

    fn capacity(&self) -> usize {
        // TODO: 実装 (2点)
    }
}

impl<T> Drop for SimpleVec<T> {
    fn drop(&mut self) {
        // TODO: 実装 (6点)
        // メモリリークを防ぐ
    }
}

fn main() {
    let mut vec = SimpleVec::new();

    vec.push(1);
    vec.push(2);
    vec.push(3);

    assert_eq!(vec.len(), 3);
    assert_eq!(vec.get(0), Some(&1));
    assert_eq!(vec.get(1), Some(&2));
    assert_eq!(vec.get(2), Some(&3));
    assert_eq!(vec.get(3), None);

    println!("Problem 1 passed!");
}

評価基準:

  • newの実装: 5点
  • pushの実装: 10点
  • getの実装: 5点
  • len/capacityの実装: 4点
  • Dropの実装: 6点

---

問題2: FFI - Cライブラリの呼び出し(30点)

標準Cライブラリの関数を呼び出すバインディングを作成しなさい。

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

// libc関数の宣言
extern "C" {
    // TODO: 以下のC関数を宣言 (10点)
    // - strlen: 文字列の長さを返す
    // - strcmp: 2つの文字列を比較
    // - getpid: プロセスIDを取得
}

// safeなラッパー関数を実装

fn safe_strlen(s: &str) -> usize {
    // TODO: 実装 (10点)
}

fn safe_strcmp(s1: &str, s2: &str) -> i32 {
    // TODO: 実装 (10点)
}

fn main() {
    // テスト
    assert_eq!(safe_strlen("Hello, World!"), 13);
    assert_eq!(safe_strcmp("abc", "abc"), 0);
    assert!(safe_strcmp("abc", "def") < 0);
    assert!(safe_strcmp("def", "abc") > 0);

    println!("Problem 2 passed!");
}

評価基準:

  • extern宣言: 10点
  • safe_strlenの実装: 10点
  • safe_strcmpの実装: 10点

---

問題3: static変数と同期化(20点)

グローバルカウンターをスレッドセーフに実装しなさい。

use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

// TODO: グローバルカウンターを実装 (10点)
static COUNTER: AtomicUsize = AtomicUsize::new(0);

fn increment() {
    // TODO: 実装 (5点)
    // カウンターをアトミックにインクリメント
}

fn get_count() -> usize {
    // TODO: 実装 (5点)
}

fn main() {
    let mut handles = vec![];

    // 10個のスレッドで各1000回インクリメント
    for _ in 0..10 {
        let handle = thread::spawn(|| {
            for _ in 0..1000 {
                increment();
            }
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    assert_eq!(get_count(), 10000);
    println!("Problem 3 passed!");
}

評価基準:

  • グローバル変数の宣言: 10点
  • incrementの実装: 5点
  • get_countの実装: 5点

---

ボーナス課題

ボーナス1: カスタムLinkedList(10点)

unsafeを使って単方向連結リストを実装しなさい。

use std::ptr;

struct Node<T> {
    data: T,
    next: *mut Node<T>,
}

struct LinkedList<T> {
    head: *mut Node<T>,
    len: usize,
}

impl<T> LinkedList<T> {
    fn new() -> Self {
        // TODO: 実装
    }

    fn push_front(&mut self, data: T) {
        // TODO: 実装
    }

    fn pop_front(&mut self) -> Option<T> {
        // TODO: 実装
    }

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

impl<T> Drop for LinkedList<T> {
    fn drop(&mut self) {
        // TODO: 実装
        // すべてのノードを解放
    }
}

fn main() {
    let mut list = LinkedList::new();

    list.push_front(1);
    list.push_front(2);
    list.push_front(3);

    assert_eq!(list.len(), 3);
    assert_eq!(list.pop_front(), Some(3));
    assert_eq!(list.pop_front(), Some(2));
    assert_eq!(list.pop_front(), Some(1));
    assert_eq!(list.pop_front(), None);

    println!("Bonus 1 passed!");
}

評価基準:

  • 基本的な実装: 5点
  • メモリ管理の正しさ: 5点

---

ボーナス2: Cの構造体との相互運用(10点)

Cと互換性のある構造体を定義し、操作しなさい。

use std::ffi::CString;
use std::os::raw::c_char;

// C互換の構造体
#[repr(C)]
struct Person {
    name: *const c_char,
    age: u32,
}

impl Person {
    fn new(name: &str, age: u32) -> Self {
        // TODO: 実装
        // CStringを使って安全に変換
    }

    fn get_name(&self) -> String {
        // TODO: 実装
        // CStrから安全にStringに変換
    }

    fn get_age(&self) -> u32 {
        self.age
    }
}

impl Drop for Person {
    fn drop(&mut self) {
        // TODO: 実装
        // nameのメモリを解放
    }
}

fn main() {
    let person = Person::new("Alice", 25);
    assert_eq!(person.get_name(), "Alice");
    assert_eq!(person.get_age(), 25);

    println!("Bonus 2 passed!");
}

評価基準:

  • C互換構造体の定義: 3点
  • 文字列の変換: 4点
  • メモリ管理: 3点

---

ボーナス3: インラインアセンブリ(10点)

インラインアセンブリを使って基本的な操作を実装しなさい。

#![feature(asm)]
use std::arch::asm;

fn add_asm(a: u64, b: u64) -> u64 {
    // TODO: 実装
    // インラインアセンブリでa + bを計算
}

fn multiply_asm(a: u64, b: u64) -> u64 {
    // TODO: 実装
    // インラインアセンブリでa * bを計算
}

fn main() {
    assert_eq!(add_asm(5, 3), 8);
    assert_eq!(multiply_asm(5, 3), 15);

    println!("Bonus 3 passed!");
}

評価基準:

  • add_asmの実装: 5点
  • multiply_asmの実装: 5点
  • : nightly Rustが必要です。

    ---

    ボーナス4: bindgenを使ったバインディング生成(10点)

    bindgenを使って実際のCライブラリのバインディングを生成しなさい。

    タスク:

  • 簡単なCライブラリを作成
  • bindgenでバインディングを生成
  • Rustから呼び出し

C側(mylib.h):

#ifndef MYLIB_H
#define MYLIB_H

typedef struct {
    int x;
    int y;
} Point;

Point point_new(int x, int y);
int point_distance_squared(Point* p1, Point* p2);

#endif

Rust側:

// build.rsでbindgenを設定
// 生成されたバインディングを使用

fn main() {
    // TODO: 実装
    // bindgenで生成されたコードを使用
}

評価基準:

  • Cライブラリの作成: 3点
  • bindgenの設定: 4点
  • バインディングの使用: 3点
  • ---

    評価基準

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

    項目 配点 評価ポイント
    問題1:rawポインタ 30点 メモリ管理の正しさ
    問題2:FFI 30点 Cライブラリとの連携
    問題3:static変数 20点 スレッドセーフな実装

    ボーナス部分(20点)

    項目 配点 評価ポイント
    ボーナス1:LinkedList 10点 unsafeの正しい使用
    ボーナス2:C互換構造体 10点 相互運用性
    ボーナス3:アセンブリ 10点 低レベル操作
    ボーナス4:bindgen 10点 実用的なツール

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

    ---

    提出方法

    ファイル構成

    rust-foundations-18/
    ├── src/
    │   ├── problem1.rs
    │   ├── problem2.rs
    │   ├── problem3.rs
    │   ├── bonus1.rs       # オプション
    │   ├── bonus2.rs       # オプション
    │   ├── bonus3.rs       # オプション
    │   └── bonus4/         # オプション
    │       ├── build.rs
    │       ├── mylib.h
    │       └── src/main.rs
    ├── Cargo.toml
    └── README.md
    

    テスト

    cargo test --release
    
    # ボーナス3(nightly必要)
    cargo +nightly test --features asm
    

    提出期限

  • マンダトリー:第18章学習後、1週間以内
  • ボーナス:余裕があれば挑戦
  • ---

    ヒント

    問題1のヒント

    // メモリ割り当て
    use std::alloc::{alloc, dealloc, Layout};
    
    let layout = Layout::array::<T>(capacity).unwrap();
    let ptr = alloc(layout) as *mut T;
    
    // 要素の書き込み
    ptr::write(self.ptr.add(self.len), value);
    

    問題2のヒント

    fn safe_strlen(s: &str) -> usize {
        let c_str = CString::new(s).unwrap();
        unsafe {
            strlen(c_str.as_ptr())
        }
    }
    

    問題3のヒント

    COUNTER.fetch_add(1, Ordering::SeqCst);
    

    ---

    学習の確認

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

  • [ ] unsafeの5つのスーパーパワー
  • [ ] rawポインタの使い方
  • [ ] FFI(Foreign Function Interface)
  • [ ] Cライブラリとの連携
  • [ ] グローバル可変変数の扱い
  • [ ] メモリ管理の責任
  • [ ] safeな抽象化の提供
  • ---

    コース完了おめでとうございます!

    Rust Foundationsコースを完了しました。これで:

  • Rustの基礎から高度な機能まで理解できました
  • 安全で高速なコードを書けるようになりました
  • 並行処理と非同期プログラミングを扱えます
  • 必要に応じてunsafeも使えます
  • 次のステップ:

  • 実際のプロジェクトを作る
  • OSSに貢献する
  • 専門分野を深める(Web、組み込み、CLI等)

Happy Rusting! 🦀