課題4: 所有権システムの理解

マンダトリー要件

問題1:所有権の基本理解(25点)

以下のコードについて、コンパイルエラーが発生するか判定し、理由を説明しなさい。

// 1-1
fn main() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1);
}

// 1-2
fn main() {
    let x = 5;
    let y = x;
    println!("{}, {}", x, y);
}

// 1-3
fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("{}, {}", s1, s2);
}

// 1-4
fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    println!("{}", s);
}

fn takes_ownership(some_string: String) {
    println!("{}", some_string);
}

// 1-5
fn main() {
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);
    println!("{}, {}", s1, s3);
}

fn gives_ownership() -> String {
    String::from("yours")
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string
}

各コードについて:

  • コンパイルできるか?(できる/できない)
  • 理由を所有権の観点から説明
  • エラーがある場合、どう修正するか(3通り示す)

提出物problem1_analysis.md

問題2:所有権を考慮した関数実装(25点)

以下の関数を所有権のルールに従って実装しなさい。

/// 文字列の長さを返す(所有権を渡して返す)
fn string_length_move(s: String) -> (String, usize) {
    // TODO: 実装
    // sの長さを計算し、sと長さのタプルを返す
}

/// 文字列に接尾辞を追加して返す
fn append_suffix(mut s: String, suffix: &str) -> String {
    // TODO: 実装
    // sにsuffixを追加してsを返す
}

/// 2つの文字列を結合して新しい文字列を返す(両方ムーブ)
fn concatenate(s1: String, s2: String) -> String {
    // TODO: 実装
}

/// 整数の配列の所有権を受け取り、合計を返す
fn sum_array(arr: Vec<i32>) -> (Vec<i32>, i32) {
    // TODO: 実装
    // 配列の合計を計算し、配列と合計のタプルを返す
}

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

    #[test]
    fn test_string_length_move() {
        let s = String::from("hello");
        let (s, len) = string_length_move(s);
        assert_eq!(len, 5);
        assert_eq!(s, "hello");
    }

    #[test]
    fn test_append_suffix() {
        let s = String::from("hello");
        let result = append_suffix(s, " world");
        assert_eq!(result, "hello world");
    }

    #[test]
    fn test_concatenate() {
        let s1 = String::from("hello");
        let s2 = String::from(" world");
        let result = concatenate(s1, s2);
        assert_eq!(result, "hello world");
    }

    #[test]
    fn test_sum_array() {
        let arr = vec![1, 2, 3, 4, 5];
        let (arr, sum) = sum_array(arr);
        assert_eq!(sum, 15);
        assert_eq!(arr, vec![1, 2, 3, 4, 5]);
    }
}

提出物problem2_ownership.rs

問題3:ムーブとコピーの実践(25点)

以下の要件を満たすプログラムを実装しなさい。

3-1: ポイント構造体

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

impl Point {
    /// 新しいPointを作成
    fn new(x: i32, y: i32) -> Point {
        // TODO: 実装
    }

    /// 原点からの距離を計算(所有権を消費)
    fn distance_from_origin(self) -> f64 {
        // TODO: 実装
        // ヒント:(x² + y²)の平方根
    }

    /// 別のPointまでの距離を計算(両方消費)
    fn distance_to(self, other: Point) -> f64 {
        // TODO: 実装
    }
}

fn main() {
    let p1 = Point::new(3, 4);
    let distance = p1.distance_from_origin();
    println!("距離: {}", distance);

    let p2 = Point::new(0, 0);
    let p3 = Point::new(3, 4);
    let distance = p2.distance_to(p3);
    println!("距離: {}", distance);
}

3-2: Copyトレイトの追加

上記のPoint構造体にCopyトレイトを追加し、動作を比較しなさい。

#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

// メソッドは同じだが、selfではなく&selfに変更
impl Point {
    fn distance_from_origin(&self) -> f64 {
        // TODO: 実装
    }
}

fn main() {
    let p1 = Point::new(3, 4);
    let distance = p1.distance_from_origin();
    println!("p1: {:?}, 距離: {}", p1, distance); // p1はまだ使える!
}

提出物

  • problem3_point_move.rs: ムーブ版
  • problem3_point_copy.rs: Copy版
  • comparison.md: 2つの違いの説明

問題4:複雑な所有権パターン(25点)

実用的なデータ構造を実装しなさい。

4-1: スタック構造

struct Stack {
    items: Vec<String>,
}

impl Stack {
    /// 新しい空のスタックを作成
    fn new() -> Stack {
        // TODO: 実装
    }

    /// 要素をプッシュ(所有権を受け取る)
    fn push(&mut self, item: String) {
        // TODO: 実装
    }

    /// 要素をポップ(所有権を返す)
    fn pop(&mut self) -> Option<String> {
        // TODO: 実装
    }

    /// スタックが空か確認
    fn is_empty(&self) -> bool {
        // TODO: 実装
    }

    /// スタックのサイズ
    fn size(&self) -> usize {
        // TODO: 実装
    }
}

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

    #[test]
    fn test_stack() {
        let mut stack = Stack::new();
        assert!(stack.is_empty());

        stack.push(String::from("first"));
        stack.push(String::from("second"));
        assert_eq!(stack.size(), 2);

        assert_eq!(stack.pop(), Some(String::from("second")));
        assert_eq!(stack.pop(), Some(String::from("first")));
        assert_eq!(stack.pop(), None);
    }
}

4-2: シンプルなメッセージキュー

struct MessageQueue {
    messages: Vec<String>,
}

impl MessageQueue {
    fn new() -> MessageQueue {
        // TODO: 実装
    }

    /// メッセージを送信(enqueue)
    fn send(&mut self, message: String) {
        // TODO: 実装
    }

    /// メッセージを受信(dequeue)
    fn receive(&mut self) -> Option<String> {
        // TODO: 実装
        // ヒント:Vec::remove(0)を使用
    }

    /// キュー内のメッセージ数
    fn count(&self) -> usize {
        // TODO: 実装
    }
}

fn main() {
    let mut queue = MessageQueue::new();

    queue.send(String::from("Hello"));
    queue.send(String::from("World"));

    while let Some(msg) = queue.receive() {
        println!("受信: {}", msg);
    }
}

提出物problem4_data_structures.rs

---

ボーナス課題

> ボーナス: 以下はオプションです。マンダトリー要件を完了してから挑戦してください。

ボーナス1:所有権の可視化ツール(10点)

所有権の動きを可視化するデバッグツールを作成しなさい。

#[derive(Debug)]
struct TrackedString {
    value: String,
    id: usize,
}

static mut COUNTER: usize = 0;

impl TrackedString {
    fn new(s: &str) -> TrackedString {
        unsafe {
            COUNTER += 1;
            let id = COUNTER;
            println!("[Create {}] '{}'", id, s);
            TrackedString {
                value: String::from(s),
                id,
            }
        }
    }
}

impl Drop for TrackedString {
    fn drop(&mut self) {
        println!("[Drop {}] '{}'", self.id, self.value);
    }
}

impl Clone for TrackedString {
    fn clone(&self) -> TrackedString {
        unsafe {
            COUNTER += 1;
            let id = COUNTER;
            println!("[Clone {} -> {}] '{}'", self.id, id, self.value);
            TrackedString {
                value: self.value.clone(),
                id,
            }
        }
    }
}

fn main() {
    println!("=== テストプログラム ===");

    let s1 = TrackedString::new("hello");
    println!("s1作成完了");

    let s2 = s1;
    println!("s1からs2へムーブ");

    let s3 = s2.clone();
    println!("s2をクローンしてs3作成");

    println!("=== 終了 ===");
}

拡張要件

  • ムーブの追跡
  • スコープの可視化
  • メモリ使用量の表示

提出物bonus1_tracker/プロジェクト

ボーナス2:所有権パズルゲーム(10点)

所有権の概念を学ぶためのパズルゲームを作成しなさい。

ゲーム概要

レベル1:基本的なムーブ
┌─────────────────┐
│ let x = "data"  │
│ let y = x       │
│                 │
│ 質問:xは使える?│
│ A) はい B) いいえ│
└─────────────────┘

レベル2:Copy vs Move
レベル3:関数呼び出し
レベル4:複雑なパターン

提出物bonus2_puzzle_game/プロジェクト

ボーナス3:カスタムスマートポインタ(10点)

独自のスマートポインタを実装しなさい。

struct MyBox<T> {
    value: T,
}

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

    fn into_inner(self) -> T {
        // TODO: 実装
        // 所有権を返す
    }
}

// Derefトレイトの実装
use std::ops::Deref;

impl<T> Deref for MyBox<T> {
    type Target = T;

    fn deref(&self) -> &T {
        // TODO: 実装
    }
}

fn main() {
    let boxed = MyBox::new(String::from("hello"));
    println!("{}", *boxed); // Derefで参照解決

    let value = boxed.into_inner();
    println!("{}", value);
}

提出物bonus3_smart_pointer.rs

ボーナス4:メモリリーク検出ツール(10点)

循環参照などのメモリリークを検出するツールを作成しなさい。

要件

  • Rcの使用を検出
  • 参照カウントの追跡
  • 循環参照の警告

use std::rc::Rc;
use std::cell::RefCell;

#[derive(Debug)]
struct Node {
    value: i32,
    next: Option<Rc<RefCell<Node>>>,
}

fn detect_cycle(node: &Rc<RefCell<Node>>) -> bool {
    // TODO: 実装
    // 循環参照を検出
}

fn main() {
    // 循環参照を作成
    let node1 = Rc::new(RefCell::new(Node {
        value: 1,
        next: None,
    }));

    let node2 = Rc::new(RefCell::new(Node {
        value: 2,
        next: Some(Rc::clone(&node1)),
    }));

    node1.borrow_mut().next = Some(Rc::clone(&node2));

    if detect_cycle(&node1) {
        println!("警告:循環参照を検出しました!");
    }
}

提出物bonus4_leak_detector/プロジェクト

ボーナス5:所有権ベンチマーク(10点)

ムーブ、クローン、参照の性能を比較しなさい。

use std::time::Instant;

fn benchmark_move() {
    let start = Instant::now();

    for _ in 0..1_000_000 {
        let s = String::from("hello");
        let _s2 = s; // ムーブ
    }

    let duration = start.elapsed();
    println!("ムーブ: {:?}", duration);
}

fn benchmark_clone() {
    let start = Instant::now();

    let s = String::from("hello");
    for _ in 0..1_000_000 {
        let _s2 = s.clone(); // クローン
    }

    let duration = start.elapsed();
    println!("クローン: {:?}", duration);
}

fn benchmark_reference() {
    let start = Instant::now();

    let s = String::from("hello");
    for _ in 0..1_000_000 {
        let _s2 = &s; // 参照(次の章で学習)
    }

    let duration = start.elapsed();
    println!("参照: {:?}", duration);
}

fn main() {
    benchmark_move();
    benchmark_clone();
    benchmark_reference();
}

拡張要件

  • 様々なサイズのデータで測定
  • メモリ使用量の測定
  • グラフ化

提出物

  • bonus5_benchmark/プロジェクト
  • benchmark_report.md: 結果分析
  • ---

    評価基準

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

    項目 配点 評価ポイント
    問題1:基本理解 25点 所有権ルールの正確な理解
    問題2:関数実装 25点 所有権を考慮した設計
    問題3:ムーブとコピー 25点 Copy/Cloneの適切な使用
    問題4:データ構造 25点 実践的な所有権管理

    ボーナス部分(20点)

    項目 配点 評価ポイント
    ボーナス1:可視化 10点 教育的な価値
    ボーナス2:ゲーム 10点 学習ツールとしての質
    ボーナス3:スマートポインタ 10点 所有権の深い理解
    ボーナス4:リーク検出 10点 実用性
    ボーナス5:ベンチマーク 10点 性能分析の質

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

    ---

    提出方法

    ファイル構成

    rust-foundations-04/
    ├── problem1_analysis.md
    ├── problem2_ownership.rs
    ├── problem3_point_move.rs
    ├── problem3_point_copy.rs
    ├── comparison.md
    ├── problem4_data_structures.rs
    └── bonus/
        ├── bonus1_tracker/
        ├── bonus2_puzzle_game/
        ├── bonus3_smart_pointer.rs
        ├── bonus4_leak_detector/
        └── bonus5_benchmark/
    

    提出期限

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

---

ヒント

問題2のヒント

タプルでの所有権の返却:

fn string_length_move(s: String) -> (String, usize) {
    let len = s.len();
    (s, len)  // タプルで両方返す
}

問題3のヒント

Copyトレイトの条件:

  • すべてのフィールドがCopyを実装している
  • ヒープメモリを使用していない

#[derive(Debug, Clone, Copy)]
struct Point {
    x: i32,  // i32はCopy
    y: i32,  // i32はCopy
    // すべてCopy → Point全体がCopyになれる
}

問題4のヒント

Vecの先頭要素を削除:

let first = vec.remove(0);  // インデックス0を削除して返す

ボーナス5のヒント

criterion crateでより正確なベンチマーク:

[dev-dependencies]
criterion = "0.5"

---

学習の確認

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

  • [ ] 所有権の3つのルール
  • [ ] ムーブセマンティクス
  • [ ] Copyトレイトの条件
  • [ ] cloneの使いどころ
  • [ ] 関数と所有権の関係
  • [ ] スコープとドロップ
  • [ ] 所有権を考慮したデータ構造設計

次の章では、所有権を移動せずに値を使う方法として参照と借用を学びます。