Day 1: 所有権マスター - 解答例

Exercise 1: 所有権の移動

解法1: clone() を使う

fn main() {
    let s1 = String::from("hello");
    let s2 = s1.clone();  // 深いコピーを作成

    // clone()により、s1とs2はそれぞれ独立したStringを所有
    // s1: ヒープ上の"hello"を指すポインタを持つ
    // s2: 別のヒープ領域にコピーされた"hello"を指すポインタを持つ
    println!("s1 = {}, s2 = {}", s1, s2);
}

メモリレイアウト:

スタック:
+--------+     ヒープ:
| s1: ptr| --> [h][e][l][l][o]
+--------+
| s2: ptr| --> [h][e][l][l][o] (別のメモリ領域)
+--------+

トレードオフ:

  • 利点: 両方の変数が独立して使える
  • 欠点: ヒープメモリを2倍消費し、コピーのコストがかかる
  • 適用場面: 元の値を保持したまま、新しい値を作りたい場合

解法2: 参照を使う

fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;  // 借用(参照)

    // s1: Stringの所有者
    // s2: s1への参照(所有権なし)
    println!("s1 = {}, s2 = {}", s1, s2);
}

メモリレイアウト:

スタック:
+--------+     ヒープ:
| s1: ptr| --> [h][e][l][l][o]
+--------+       ^
| s2: ptr|-------+ (s1を指す参照)
+--------+

トレードオフ:

  • 利点: メモリコピーなし、高速
  • 欠点: s2はs1が生きている間しか使えない
  • 適用場面: 読み取り専用でデータにアクセスしたい場合

解法3: Rc を使う(上級編)

use std::rc::Rc;

fn main() {
    let s1 = Rc::new(String::from("hello"));
    let s2 = Rc::clone(&s1);  // 参照カウントのクローン(データはコピーしない)

    // s1とs2は同じデータへの参照を共有
    // 参照カウント: 2
    println!("s1 = {}, s2 = {}", s1, s2);
    println!("参照カウント: {}", Rc::strong_count(&s1));
}

トレードオフ:

  • 利点: データのコピーなし、複数の所有者が持てる
  • 欠点: 参照カウントのオーバーヘッド、スレッド間では使えない
  • 適用場面: 複数の所有者が必要で、シングルスレッドの場合

Exercise 2: 関数への所有権渡し

解法1: 所有権を移動して返す(基本パターン)

// 所有権を受け取り、変換し、所有権を返す
fn take_ownership(s: String) -> String {
    // s: この関数がStringの所有者
    // to_uppercase()は新しいStringを作成して返す
    s.to_uppercase()
    // sは関数の終わりで破棄される(が、返す前に所有権が移動)
}

fn main() {
    let s = String::from("hello");
    // sの所有権がtake_ownershipに移動
    let s = take_ownership(s);
    // 戻り値で新しい所有権を受け取る
    println!("{}", s); // HELLO
}

所有権の流れ:

main の s → take_ownership の s → main の s (新しい値)
  "hello"        "hello"              "HELLO"

解法2: 借用を使う(推奨パターン)

// 参照を受け取り、新しいStringを返す
fn to_upper(s: &str) -> String {
    // s: 借用(所有権なし)
    s.to_uppercase()
}

fn main() {
    let s = String::from("hello");
    // sを借用として渡す
    let upper = to_upper(&s);
    // sはまだ使える!
    println!("元: {}, 大文字: {}", s, upper);
}

トレードオフ:

  • 利点: 元の値を保持できる、より柔軟
  • 適用場面: 元の値も後で使う可能性がある場合

解法3: in-place変更(可変参照)

// 可変参照を受け取り、その場で変更
fn make_uppercase(s: &mut String) {
    // s: 可変借用
    *s = s.to_uppercase();
}

fn main() {
    let mut s = String::from("hello");
    // 可変借用として渡す
    make_uppercase(&mut s);
    // sの中身が変更されている
    println!("{}", s); // HELLO
}

トレードオフ:

  • 利点: メモリ効率が良い、新しいStringを作らない
  • 欠点: 元の値が上書きされる
  • 適用場面: メモリ効率を重視し、元の値が不要な場合

Exercise 3: Copy trait

完全な実装例

fn main() {
    // ========== Part 1: Copy trait を持つ型 ==========

    // i32 は Copy trait を実装している
    // スタック上のデータをそのままコピーできる
    let x = 5;
    let y = x;  // ビット単位のコピー

    // 両方が独立した値を持つ
    println!("x = {}, y = {}", x, y);  // x = 5, y = 5

    // xを変更してもyには影響しない
    let mut x = 5;
    let y = x;
    x = 10;
    println!("x = {}, y = {}", x, y);  // x = 10, y = 5

    // ========== Part 2: Copy を持たない型 ==========

    // String は Copy を実装していない
    // ヒープ上のデータを持つため、コピーは高コスト
    let s1 = String::from("hello");
    let s2 = s1;  // Move: s1の所有権がs2に移動

    // s1は無効になる
    // println!("{}", s1);  // コンパイルエラー!
    // エラーメッセージ: borrow of moved value: `s1`

    println!("{}", s2);  // OK: s2が所有者

    // ========== Part 3: 両方使いたい場合 ==========

    // 両方使いたい場合は clone()
    let s3 = String::from("world");
    let s4 = s3.clone();  // 明示的なコピー

    // 両方使える(独立した2つのStringが存在)
    println!("s3 = {}, s4 = {}", s3, s4);

    // ========== Part 4: なぜこの違いがあるのか ==========

    // Copy可能な型の条件:
    // 1. スタック上に完全に格納できる
    // 2. ヒープなどの外部リソースを持たない
    // 3. Dropトレイトを実装していない

    // Copy可能な型の例
    let numbers = [1, 2, 3, 4, 5];  // 固定長配列
    let numbers_copy = numbers;  // Copy
    println!("元: {:?}, コピー: {:?}", numbers, numbers_copy);

    // Copy不可能な型の例
    let vec = vec![1, 2, 3, 4, 5];  // 動的配列(ヒープ使用)
    let vec2 = vec;  // Move
    // println!("{:?}", vec);  // エラー!
    println!("vec2: {:?}", vec2);
}

より詳細な例:カスタム型でのCopy

// Copy可能な構造体
#[derive(Copy, Clone, Debug)]
struct Point {
    x: i32,
    y: i32,
}

// Copy不可能な構造体(Stringフィールドを持つ)
#[derive(Debug)]
struct Person {
    name: String,  // Stringは Copy ではない
    age: u32,
}

fn main() {
    // Point は Copy
    let p1 = Point { x: 5, y: 10 };
    let p2 = p1;  // Copy
    println!("p1: {:?}, p2: {:?}", p1, p2);  // 両方使える

    // Person は Copy ではない
    let person1 = Person {
        name: String::from("Alice"),
        age: 30,
    };
    let person2 = person1;  // Move
    // println!("{:?}", person1);  // エラー!
    println!("{:?}", person2);

    // Personを両方使いたい場合はCloneを実装
}

// Clone を実装したPerson
#[derive(Clone, Debug)]
struct CloneablePerson {
    name: String,
    age: u32,
}

fn demonstrate_clone() {
    let person1 = CloneablePerson {
        name: String::from("Bob"),
        age: 25,
    };
    let person2 = person1.clone();  // 明示的なクローン
    println!("{:?}, {:?}", person1, person2);  // 両方使える
}

よくある間違いと修正方法

間違い1: 移動後の値を使おうとする

// ❌ 間違い
fn wrong_example1() {
    let s = String::from("hello");
    let t = s;  // sの所有権がtに移動
    println!("{}", s);  // コンパイルエラー!
}

// ✅ 修正1: cloneを使う
fn correct_example1a() {
    let s = String::from("hello");
    let t = s.clone();
    println!("{}", s);  // OK
}

// ✅ 修正2: 借用を使う(推奨)
fn correct_example1b() {
    let s = String::from("hello");
    let t = &s;  // 借用
    println!("{}", s);  // OK
}

間違い2: 関数に渡した後に使おうとする

// ❌ 間違い
fn consume(s: String) {
    println!("{}", s);
}

fn wrong_example2() {
    let s = String::from("hello");
    consume(s);  // sの所有権が移動
    println!("{}", s);  // コンパイルエラー!
}

// ✅ 修正1: 所有権を返す
fn consume_and_return(s: String) -> String {
    println!("{}", s);
    s  // 所有権を返す
}

fn correct_example2a() {
    let s = String::from("hello");
    let s = consume_and_return(s);
    println!("{}", s);  // OK
}

// ✅ 修正2: 借用を使う(推奨)
fn borrow_only(s: &str) {
    println!("{}", s);
}

fn correct_example2b() {
    let s = String::from("hello");
    borrow_only(&s);
    println!("{}", s);  // OK
}

間違い3: 不要なclone()

// ❌ パフォーマンスに悪い
fn inefficient_example() {
    let s = String::from("hello");

    // sをこの後使わないのにclone()している
    process_string(s.clone());

    // sはもう使わない
}

fn process_string(s: String) {
    println!("{}", s);
}

// ✅ 改善版
fn efficient_example() {
    let s = String::from("hello");

    // 所有権を渡す(cloneなし)
    process_string(s);

    // sは使えないが、それで問題ない
}

最適化パス

レベル1: 基本的な所有権移動

// 初心者向け: 動くが最適ではない
fn level1_process(data: Vec<i32>) -> Vec<i32> {
    let mut result = Vec::new();
    for item in data {
        result.push(item * 2);
    }
    result
}

レベル2: 借用を活用

// 中級者向け: 借用で効率化
fn level2_process(data: &[i32]) -> Vec<i32> {
    let mut result = Vec::new();
    for &item in data {
        result.push(item * 2);
    }
    result
}

// 呼び出し側で元のデータも使える
fn use_level2() {
    let data = vec![1, 2, 3, 4, 5];
    let result = level2_process(&data);
    println!("元: {:?}, 結果: {:?}", data, result);
}

レベル3: イテレータで関数型スタイル

// 上級者向け: ゼロコスト抽象化
fn level3_process(data: &[i32]) -> Vec<i32> {
    data.iter()
        .map(|&x| x * 2)
        .collect()
}

// コンパイル後は手書きのループと同等のパフォーマンス

レベル4: in-place変更でメモリ効率最大化

// エキスパート向け: メモリ割り当てを最小化
fn level4_process(data: &mut [i32]) {
    for item in data.iter_mut() {
        *item *= 2;
    }
}

// 呼び出し側
fn use_level4() {
    let mut data = vec![1, 2, 3, 4, 5];
    level4_process(&mut data);
    // dataが直接変更される(新しいVecを作らない)
    println!("{:?}", data);
}

ベンチマーク結果

use std::time::Instant;

fn benchmark_ownership_patterns() {
    let data: Vec<i32> = (0..1_000_000).collect();

    // パターン1: clone()を使う
    let start = Instant::now();
    let data_clone = data.clone();
    let result1 = process_by_clone(data_clone);
    println!("Clone: {:?}", start.elapsed());
    // 結果: ~5ms (データのコピーコスト)

    // パターン2: 借用を使う
    let start = Instant::now();
    let result2 = process_by_borrow(&data);
    println!("Borrow: {:?}", start.elapsed());
    // 結果: ~3ms (コピーなし)

    // パターン3: in-place変更
    let start = Instant::now();
    let mut data_mut = data.clone();
    process_in_place(&mut data_mut);
    println!("In-place: {:?}", start.elapsed());
    // 結果: ~1ms (新しいVec不要)
}

fn process_by_clone(mut data: Vec<i32>) -> Vec<i32> {
    for item in &mut data {
        *item *= 2;
    }
    data
}

fn process_by_borrow(data: &[i32]) -> Vec<i32> {
    data.iter().map(|&x| x * 2).collect()
}

fn process_in_place(data: &mut [i32]) {
    for item in data {
        *item *= 2;
    }
}

パフォーマンス比較:

  • Clone: 5ms(データコピー + 処理)
  • Borrow: 3ms(処理のみ、新しいVec作成)
  • In-place: 1ms(最も効率的、メモリ割り当てなし)

実践的なユースケース

ケース1: 設定データの管理

#[derive(Clone, Debug)]
struct Config {
    host: String,
    port: u16,
    timeout: u32,
}

impl Config {
    // 所有権を取る(この後Configを変更する場合)
    fn validate(mut self) -> Result<Config, String> {
        if self.port == 0 {
            return Err("ポートは0にできません".to_string());
        }
        self.timeout = self.timeout.max(1000);  // 最低1秒
        Ok(self)
    }

    // 借用する(読み取り専用)
    fn display(&self) {
        println!("{}:{} (timeout: {}ms)", self.host, self.port, self.timeout);
    }

    // 可変借用する(一部を変更)
    fn update_timeout(&mut self, timeout: u32) {
        self.timeout = timeout;
    }
}

fn main() {
    let config = Config {
        host: "localhost".to_string(),
        port: 8080,
        timeout: 500,
    };

    let config = config.validate().expect("設定が無効");
    config.display();
}

ケース2: データ処理パイプライン

fn data_pipeline() {
    let data = vec![1, 2, 3, 4, 5];

    // 所有権を渡しながらパイプライン処理
    let result = data
        .into_iter()  // 所有権を消費
        .map(|x| x * 2)
        .filter(|x| x > &5)
        .collect::<Vec<_>>();

    println!("{:?}", result);
    // dataはもう使えない(所有権が移動した)
}

次のステップ

このExerciseで学んだこと:

  • Move vs Copy: 型によって挙動が異なる
  • Clone: 明示的なコピーが必要な場合
  • 借用: 所有権を移動せずにアクセス
  • パフォーマンス: 適切なパターンを選ぶことで最適化

次のDay 2では、借用についてさらに深く学びます:

  • 不変借用 vs 可変借用
  • 借用のルール
  • ライフタイムの基礎
  • 借用チェッカーのエラーを読み解く

所有権を完全に理解することは、Rustの最大の難関を越えることを意味します。頑張ってください!