課題5: 参照と借用の実践

マンダトリー要件

問題1:借用ルールの理解(25点)

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

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

// 1-2
fn main() {
    let mut s = String::from("hello");
    let r1 = &s;
    let r2 = &mut s;
    println!("{}, {}", r1, r2);
}

// 1-3
fn main() {
    let mut s = String::from("hello");
    let r1 = &mut s;
    let r2 = &mut s;
    println!("{}, {}", r1, r2);
}

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

// 1-5
fn main() {
    let s = String::from("hello");
    change(&s);
}

fn change(some_string: &String) {
    some_string.push_str(", world");
}

// 1-6
fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");
    &s
}

各コードについて:

  • コンパイルできるか?
  • エラーの場合、どのルールに違反しているか?
  • どう修正するか?
  • 提出物problem1_analysis.md

    問題2:参照を使った関数実装(25点)

    以下の関数を参照と借用を使って実装しなさい。

    /// 文字列の長さを返す(不変参照)
    fn string_length(s: &String) -> usize {
        // TODO: 実装
    }
    
    /// 文字列に接尾辞を追加(可変参照)
    fn append_suffix(s: &mut String, suffix: &str) {
        // TODO: 実装
    }
    
    /// 最初の単語を返す(スライスを返す)
    fn first_word(s: &str) -> &str {
        // TODO: 実装
        // ヒント:スペースを見つけてその前までを返す
    }
    
    /// 配列の最大値を返す(参照を返す)
    fn find_max<'a>(numbers: &'a [i32]) -> Option<&'a i32> {
        // TODO: 実装
    }
    
    /// 2つの文字列のうち長い方を返す
    fn longer<'a>(s1: &'a str, s2: &'a str) -> &'a str {
        // TODO: 実装
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_string_length() {
            let s = String::from("hello");
            assert_eq!(string_length(&s), 5);
            assert_eq!(s, "hello"); // sはまだ有効
        }
    
        #[test]
        fn test_append_suffix() {
            let mut s = String::from("hello");
            append_suffix(&mut s, " world");
            assert_eq!(s, "hello world");
        }
    
        #[test]
        fn test_first_word() {
            assert_eq!(first_word("hello world"), "hello");
            assert_eq!(first_word("hello"), "hello");
        }
    
        #[test]
        fn test_find_max() {
            let numbers = [1, 5, 3, 9, 2];
            assert_eq!(find_max(&numbers), Some(&9));
    
            let empty: [i32; 0] = [];
            assert_eq!(find_max(&empty), None);
        }
    
        #[test]
        fn test_longer() {
            assert_eq!(longer("hello", "world!"), "world!");
            assert_eq!(longer("rust", "go"), "rust");
        }
    }
    

    提出物problem2_references.rs

    問題3:文字列処理(25点)

    文字列処理ライブラリを実装しなさい。

    /// 文字列を反転する(元の文字列を変更)
    fn reverse_string(s: &mut String) {
        // TODO: 実装
        // ヒント:chars().rev().collect()
    }
    
    /// 文字列から空白を削除する
    fn remove_whitespace(s: &mut String) {
        // TODO: 実装
    }
    
    /// 文字列を単語に分割する
    fn split_words(s: &str) -> Vec<&str> {
        // TODO: 実装
        // ヒント:split_whitespace()
    }
    
    /// パリンドローム(回文)かどうか判定
    fn is_palindrome(s: &str) -> bool {
        // TODO: 実装
        // ヒント:大文字小文字を無視、空白を無視
    }
    
    /// 文字列内の単語数をカウント
    fn count_words(s: &str) -> usize {
        // TODO: 実装
    }
    
    /// 指定された文字列が含まれているか
    fn contains_substring(haystack: &str, needle: &str) -> bool {
        // TODO: 実装
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_reverse_string() {
            let mut s = String::from("hello");
            reverse_string(&mut s);
            assert_eq!(s, "olleh");
        }
    
        #[test]
        fn test_remove_whitespace() {
            let mut s = String::from("hello world rust");
            remove_whitespace(&mut s);
            assert_eq!(s, "helloworldrust");
        }
    
        #[test]
        fn test_split_words() {
            let s = "hello world rust";
            let words = split_words(s);
            assert_eq!(words, vec!["hello", "world", "rust"]);
        }
    
        #[test]
        fn test_is_palindrome() {
            assert!(is_palindrome("racecar"));
            assert!(is_palindrome("A man a plan a canal Panama"));
            assert!(!is_palindrome("hello"));
        }
    
        #[test]
        fn test_count_words() {
            assert_eq!(count_words("hello world"), 2);
            assert_eq!(count_words("  hello   world  "), 2);
        }
    }
    

    提出物problem3_string_utils.rs

    問題4:配列処理(25点)

    配列操作関数を実装しなさい。

    /// 配列の合計を計算
    fn sum(numbers: &[i32]) -> i32 {
        // TODO: 実装
    }
    
    /// 配列の平均を計算
    fn average(numbers: &[i32]) -> f64 {
        // TODO: 実装
    }
    
    /// 配列の最小値と最大値を返す
    fn min_max(numbers: &[i32]) -> Option<(i32, i32)> {
        // TODO: 実装
        // 空配列の場合はNone
    }
    
    /// 配列を逆順にする(元の配列を変更)
    fn reverse_array(arr: &mut [i32]) {
        // TODO: 実装
    }
    
    /// 配列から重複を削除(ソート済み配列を想定)
    fn dedup(arr: &mut Vec<i32>) {
        // TODO: 実装
        // ヒント:Vec::dedup()
    }
    
    /// 配列の要素をすべて2倍にする
    fn double_elements(arr: &mut [i32]) {
        // TODO: 実装
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_sum() {
            assert_eq!(sum(&[1, 2, 3, 4, 5]), 15);
        }
    
        #[test]
        fn test_average() {
            assert_eq!(average(&[1, 2, 3, 4, 5]), 3.0);
        }
    
        #[test]
        fn test_min_max() {
            assert_eq!(min_max(&[3, 1, 4, 1, 5]), Some((1, 5)));
            assert_eq!(min_max(&[]), None);
        }
    
        #[test]
        fn test_reverse_array() {
            let mut arr = [1, 2, 3, 4, 5];
            reverse_array(&mut arr);
            assert_eq!(arr, [5, 4, 3, 2, 1]);
        }
    
        #[test]
        fn test_double_elements() {
            let mut arr = [1, 2, 3, 4, 5];
            double_elements(&mut arr);
            assert_eq!(arr, [2, 4, 6, 8, 10]);
        }
    }
    

    提出物problem4_array_utils.rs

    ---

    ボーナス課題

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

    ボーナス1:テキストエディタ(10点)

    シンプルなテキストエディタのコア機能を実装しなさい。

    struct TextBuffer {
        content: String,
    }
    
    impl TextBuffer {
        fn new() -> Self {
            TextBuffer {
                content: String::new(),
            }
        }
    
        /// テキストを追加
        fn append(&mut self, text: &str) {
            // TODO: 実装
        }
    
        /// 指定位置にテキストを挿入
        fn insert(&mut self, pos: usize, text: &str) {
            // TODO: 実装
        }
    
        /// 指定範囲のテキストを削除
        fn delete(&mut self, start: usize, end: usize) {
            // TODO: 実装
        }
    
        /// テキスト全体を取得
        fn content(&self) -> &str {
            // TODO: 実装
        }
    
        /// 指定行を取得
        fn get_line(&self, line_num: usize) -> Option<&str> {
            // TODO: 実装
        }
    
        /// 行数を取得
        fn line_count(&self) -> usize {
            // TODO: 実装
        }
    
        /// 検索(マッチした行番号を返す)
        fn search(&self, pattern: &str) -> Vec<usize> {
            // TODO: 実装
        }
    
        /// 置換
        fn replace_all(&mut self, from: &str, to: &str) {
            // TODO: 実装
        }
    }
    
    fn main() {
        let mut buffer = TextBuffer::new();
        buffer.append("Hello, World!\n");
        buffer.append("This is Rust.\n");
        buffer.insert(0, "First line\n");
    
        println!("{}", buffer.content());
        println!("行数: {}", buffer.line_count());
    
        let results = buffer.search("Rust");
        println!("検索結果: {:?}", results);
    }
    

    提出物bonus1_text_editor/プロジェクト

    ボーナス2:データ検証ライブラリ(10点)

    参照を活用したバリデーションライブラリを作成しなさい。

    struct Validator;
    
    impl Validator {
        /// 空でないことを検証
        fn is_not_empty(s: &str) -> Result<(), &'static str> {
            // TODO: 実装
        }
    
        /// 最小長を検証
        fn min_length(s: &str, min: usize) -> Result<(), String> {
            // TODO: 実装
        }
    
        /// 最大長を検証
        fn max_length(s: &str, max: usize) -> Result<(), String> {
            // TODO: 実装
        }
    
        /// メールアドレス形式を検証
        fn is_email(s: &str) -> Result<(), &'static str> {
            // TODO: 実装
            // 簡易版:@を含むかチェック
        }
    
        /// 数値のみかを検証
        fn is_numeric(s: &str) -> Result<(), &'static str> {
            // TODO: 実装
        }
    
        /// 範囲内の数値かを検証
        fn in_range(value: i32, min: i32, max: i32) -> Result<(), String> {
            // TODO: 実装
        }
    }
    
    fn validate_user_input(username: &str, email: &str, age: i32) -> Result<(), Vec<String>> {
        let mut errors = Vec::new();
    
        if let Err(e) = Validator::min_length(username, 3) {
            errors.push(e);
        }
    
        if let Err(e) = Validator::is_email(email) {
            errors.push(e.to_string());
        }
    
        if let Err(e) = Validator::in_range(age, 0, 120) {
            errors.push(e);
        }
    
        if errors.is_empty() {
            Ok(())
        } else {
            Err(errors)
        }
    }
    
    fn main() {
        match validate_user_input("ab", "invalid", 150) {
            Ok(_) => println!("検証成功"),
            Err(errors) => {
                println!("検証エラー:");
                for error in errors {
                    println!("  - {}", error);
                }
            }
        }
    }
    

    提出物bonus2_validator/プロジェクト

    ボーナス3:キャッシュシステム(10点)

    参照を活用したキャッシュシステムを実装しなさい。

    use std::collections::HashMap;
    
    struct Cache<'a> {
        data: HashMap<&'a str, String>,
    }
    
    impl<'a> Cache<'a> {
        fn new() -> Self {
            Cache {
                data: HashMap::new(),
            }
        }
    
        /// キャッシュに保存
        fn set(&mut self, key: &'a str, value: String) {
            // TODO: 実装
        }
    
        /// キャッシュから取得
        fn get(&self, key: &str) -> Option<&str> {
            // TODO: 実装
        }
    
        /// キャッシュから削除
        fn remove(&mut self, key: &str) -> Option<String> {
            // TODO: 実装
        }
    
        /// キャッシュをクリア
        fn clear(&mut self) {
            // TODO: 実装
        }
    
        /// キーが存在するか
        fn contains(&self, key: &str) -> bool {
            // TODO: 実装
        }
    }
    
    fn main() {
        let mut cache = Cache::new();
    
        cache.set("user:1", String::from("Alice"));
        cache.set("user:2", String::from("Bob"));
    
        if let Some(name) = cache.get("user:1") {
            println!("ユーザー1: {}", name);
        }
    
        cache.remove("user:2");
        println!("user:2が存在するか: {}", cache.contains("user:2"));
    }
    

    提出物bonus3_cache/プロジェクト

    ボーナス4:JSONパーサー(簡易版)(10点)

    参照を活用したJSONパーサーを実装しなさい。

    #[derive(Debug, PartialEq)]
    enum JsonValue<'a> {
        Null,
        Bool(bool),
        Number(f64),
        String(&'a str),
        Array(Vec<JsonValue<'a>>),
        Object(Vec<(&'a str, JsonValue<'a>)>),
    }
    
    fn parse_string(input: &str) -> Option<&str> {
        // TODO: 簡易実装
        // ダブルクォートで囲まれた部分を返す
    }
    
    fn parse_number(input: &str) -> Option<f64> {
        // TODO: 実装
    }
    
    fn parse_bool(input: &str) -> Option<bool> {
        // TODO: 実装
    }
    
    fn main() {
        // テスト
        let json = r#"{"name": "Alice", "age": 30, "active": true}"#;
        // TODO: パース実装
    }
    

    提出物bonus4_json_parser/プロジェクト

    ボーナス5:参照カウンター(10点)

    参照カウントを手動で実装しなさい。

    use std::cell::Cell;
    
    struct RefCounter<T> {
        value: T,
        count: Cell<usize>,
    }
    
    impl<T> RefCounter<T> {
        fn new(value: T) -> Self {
            RefCounter {
                value,
                count: Cell::new(1),
            }
        }
    
        fn increment(&self) {
            // TODO: 実装
        }
    
        fn decrement(&self) -> usize {
            // TODO: 実装
        }
    
        fn count(&self) -> usize {
            // TODO: 実装
        }
    
        fn get(&self) -> &T {
            // TODO: 実装
        }
    }
    
    fn main() {
        let counter = RefCounter::new(String::from("hello"));
    
        counter.increment();
        counter.increment();
        println!("参照カウント: {}", counter.count());
    
        counter.decrement();
        println!("参照カウント: {}", counter.count());
    
        println!("値: {}", counter.get());
    }
    

    提出物bonus5_ref_counter/プロジェクト

    ---

    評価基準

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

    項目 配点 評価ポイント
    問題1:借用ルール 25点 ルールの正確な理解
    問題2:参照関数 25点 適切な参照の使用
    問題3:文字列処理 25点 スライスの活用
    問題4:配列処理 25点 可変/不変参照の使い分け

    ボーナス部分(20点)

    項目 配点 評価ポイント
    ボーナス1:エディタ 10点 実用性と設計
    ボーナス2:バリデーション 10点 エラー処理の質
    ボーナス3:キャッシュ 10点 ライフタイムの理解
    ボーナス4:パーサー 10点 スライスの高度な利用
    ボーナス5:参照カウンター 10点 内部可変性の理解

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

    ---

    提出方法

    ファイル構成

    rust-foundations-05/
    ├── problem1_analysis.md
    ├── problem2_references.rs
    ├── problem3_string_utils.rs
    ├── problem4_array_utils.rs
    └── bonus/
        ├── bonus1_text_editor/
        ├── bonus2_validator/
        ├── bonus3_cache/
        ├── bonus4_json_parser/
        └── bonus5_ref_counter/
    

    提出期限

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

---

ヒント

問題2のヒント

first_word関数:

fn first_word(s: &str) -> &str {
    for (i, &byte) in s.as_bytes().iter().enumerate() {
        if byte == b' ' {
            return &s[..i];
        }
    }
    &s[..]
}

問題3のヒント

パリンドローム判定:

fn is_palindrome(s: &str) -> bool {
    let cleaned: String = s.chars()
        .filter(|c| c.is_alphanumeric())
        .map(|c| c.to_ascii_lowercase())
        .collect();

    cleaned == cleaned.chars().rev().collect::<String>()
}

問題4のヒント

配列の逆順:

fn reverse_array(arr: &mut [i32]) {
    arr.reverse();  // 組み込みメソッド

    // または手動で
    let len = arr.len();
    for i in 0..len/2 {
        arr.swap(i, len - 1 - i);
    }
}

---

学習の確認

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

  • [ ] 不変参照と可変参照の違い
  • [ ] 借用の3つのルール
  • [ ] 参照のスコープ(NLL)
  • [ ] ダングリング参照の防止
  • [ ] スライスの使い方
  • [ ] &strとStringの使い分け
  • [ ] 関数引数での参照の選択

次の章では、ライフタイムを学びます。参照の有効期間を明示的に指定する方法です。