Skip to content

第3章 練習問題 - 反転術式の試練

Option型・Result型・エラーハンドリングの総合演習

第3章で学んだ反転術式編の内容を実践で確認しよう。Option型、Result型、高度なエラーハンドリング - これらは俺の反転術式のように、一見ネガティブな状況を強力な力に変える技術だ。

五条先生からのアドバイス

この章の問題は、実際のプロジェクトでよく遭遇するパターンばかりだ。 エラーハンドリングは最初は面倒に感じるかもしれないが、一度慣れれば絶対的な安全性を提供してくれる。

初級編 - Option型の基本操作

問題1: 呪術師検索システム

呪術師のデータベースからOption型を使って安全に検索するシステムを作成せよ。

#[derive(Debug, Clone)]
struct Sorcerer {
    name: String,
    power: i32,
    grade: String,
}

struct SorcererDatabase {
    sorcerers: Vec<Sorcerer>,
}

impl SorcererDatabase {
    fn new() -> Self {
        // ここに実装
    }

    fn add_sorcerer(&mut self, sorcerer: Sorcerer) {
        // ここに実装
    }

    // 以下のメソッドを実装せよ:
    // 1. 名前で検索 (完全一致)
    // 2. 名前で検索 (部分一致)
    // 3. 最も強い呪術師を取得
    // 4. 特定の呪力以上の呪術師を検索
    // 5. 特定の等級の呪術師を検索
}

fn main() {
    // テスト用データ
    let sorcerers = vec![
        Sorcerer { name: "五条悟".to_string(), power: 3000, grade: "特級".to_string() },
        Sorcerer { name: "両面宿儺".to_string(), power: 2800, grade: "特級".to_string() },
        Sorcerer { name: "虎杖悠仁".to_string(), power: 1200, grade: "1級".to_string() },
    ];

    // システムのテスト
}
解答を見る
#[derive(Debug, Clone)]
struct Sorcerer {
    name: String,
    power: i32,
    grade: String,
}

struct SorcererDatabase {
    sorcerers: Vec<Sorcerer>,
}

impl SorcererDatabase {
    fn new() -> Self {
        SorcererDatabase {
            sorcerers: Vec::new(),
        }
    }

    fn add_sorcerer(&mut self, sorcerer: Sorcerer) {
        self.sorcerers.push(sorcerer);
    }

    // 1. 名前で検索 (完全一致)
    fn find_by_name(&self, name: &str) -> Option<&Sorcerer> {
        self.sorcerers.iter()
            .find(|sorcerer| sorcerer.name == name)
    }

    // 2. 名前で検索 (部分一致)
    fn find_by_partial_name(&self, partial_name: &str) -> Option<&Sorcerer> {
        self.sorcerers.iter()
            .find(|sorcerer| sorcerer.name.contains(partial_name))
    }

    // 3. 最も強い呪術師を取得
    fn find_strongest(&self) -> Option<&Sorcerer> {
        self.sorcerers.iter()
            .max_by_key(|sorcerer| sorcerer.power)
    }

    // 4. 特定の呪力以上の呪術師を検索
    fn find_by_min_power(&self, min_power: i32) -> Option<Vec<&Sorcerer>> {
        let filtered: Vec<&Sorcerer> = self.sorcerers.iter()
            .filter(|sorcerer| sorcerer.power >= min_power)
            .collect();

        if filtered.is_empty() {
            None
        } else {
            Some(filtered)
        }
    }

    // 5. 特定の等級の呪術師を検索
    fn find_by_grade(&self, grade: &str) -> Option<Vec<&Sorcerer>> {
        let filtered: Vec<&Sorcerer> = self.sorcerers.iter()
            .filter(|sorcerer| sorcerer.grade == grade)
            .collect();

        if filtered.is_empty() {
            None
        } else {
            Some(filtered)
        }
    }

    // ボーナス: 複合検索
    fn find_by_criteria(&self, min_power: Option<i32>, grade: Option<&str>) -> Vec<&Sorcerer> {
        self.sorcerers.iter()
            .filter(|sorcerer| {
                let power_ok = min_power.map_or(true, |p| sorcerer.power >= p);
                let grade_ok = grade.map_or(true, |g| sorcerer.grade == g);
                power_ok && grade_ok
            })
            .collect()
    }
}

fn main() {
    let mut db = SorcererDatabase::new();

    // テスト用データ
    let sorcerers = vec![
        Sorcerer { name: "五条悟".to_string(), power: 3000, grade: "特級".to_string() },
        Sorcerer { name: "両面宿儺".to_string(), power: 2800, grade: "特級".to_string() },
        Sorcerer { name: "虎杖悠仁".to_string(), power: 1200, grade: "1級".to_string() },
        Sorcerer { name: "伏黒恵".to_string(), power: 1000, grade: "2級".to_string() },
        Sorcerer { name: "釘崎野薔薇".to_string(), power: 900, grade: "3級".to_string() },
    ];

    for sorcerer in sorcerers {
        db.add_sorcerer(sorcerer);
    }

    println!("=== 呪術師検索システム ===");

    // 1. 完全一致検索
    match db.find_by_name("五条悟") {
        Some(sorcerer) => println!("✓ 見つかりました: {} (呪力: {})", sorcerer.name, sorcerer.power),
        None => println!("✗ 見つかりませんでした"),
    }

    // 2. 部分一致検索
    match db.find_by_partial_name("虎杖") {
        Some(sorcerer) => println!("✓ 部分一致: {} (呪力: {})", sorcerer.name, sorcerer.power),
        None => println!("✗ 部分一致なし"),
    }

    // 3. 最強検索
    if let Some(strongest) = db.find_strongest() {
        println!("✓ 最強: {} (呪力: {})", strongest.name, strongest.power);
    }

    // 4. 呪力フィルタ
    match db.find_by_min_power(2000) {
        Some(powerful) => {
            println!("✓ 呪力2000以上:");
            for sorcerer in powerful {
                println!("  {} (呪力: {})", sorcerer.name, sorcerer.power);
            }
        },
        None => println!("✗ 該当者なし"),
    }

    // 5. 等級検索
    match db.find_by_grade("特級") {
        Some(special_grade) => {
            println!("✓ 特級呪術師:");
            for sorcerer in special_grade {
                println!("  {} (呪力: {})", sorcerer.name, sorcerer.power);
            }
        },
        None => println!("✗ 特級呪術師なし"),
    }

    // ボーナス: 複合検索
    let criteria_result = db.find_by_criteria(Some(1000), Some("特級"));
    println!("\\n複合検索(呪力1000以上 かつ 特級):");
    for sorcerer in criteria_result {
        println!("  {} (呪力: {}, 等級: {})", sorcerer.name, sorcerer.power, sorcerer.grade);
    }
}

問題2: Option型のチェーン操作

複数のOption型を組み合わせて、安全にデータを処理するシステムを作成せよ。

#[derive(Debug)]
struct TechniqueInfo {
    name: String,
    power: i32,
    element: String,
}

struct TechniqueDatabase {
    techniques: Vec<TechniqueInfo>,
}

// 以下の関数を実装せよ:
// 1. 術式名から威力を取得
// 2. 威力から推奨レベルを計算
// 3. 複数の術式の合計威力を計算
// 4. 術式の組み合わせ効果を計算

fn main() {
    let techniques = vec![
        TechniqueInfo { name: "蒼".to_string(), power: 1000, element: "無下限".to_string() },
        TechniqueInfo { name: "赫".to_string(), power: 1500, element: "無下限".to_string() },
        TechniqueInfo { name: "茈".to_string(), power: 3000, element: "無下限".to_string() },
    ];

    // チェーン操作のテスト
}
解答を見る
#[derive(Debug)]
struct TechniqueInfo {
    name: String,
    power: i32,
    element: String,
}

struct TechniqueDatabase {
    techniques: Vec<TechniqueInfo>,
}

impl TechniqueDatabase {
    fn new(techniques: Vec<TechniqueInfo>) -> Self {
        TechniqueDatabase { techniques }
    }

    // 1. 術式名から威力を取得
    fn get_power(&self, name: &str) -> Option<i32> {
        self.techniques.iter()
            .find(|tech| tech.name == name)
            .map(|tech| tech.power)
    }

    // 2. 威力から推奨レベルを計算
    fn calculate_recommended_level(&self, power: i32) -> Option<String> {
        if power <= 0 {
            return None;
        }

        let level = match power {
            1..=500 => "初心者",
            501..=1000 => "中級者",
            1001..=2000 => "上級者",
            2001..=3000 => "特級",
            _ => "最強級",
        };

        Some(level.to_string())
    }

    // 3. 複数の術式の合計威力を計算
    fn calculate_total_power(&self, technique_names: &[&str]) -> Option<i32> {
        let mut total = 0;

        for name in technique_names {
            let power = self.get_power(name)?;  // 1つでも見つからなければNone
            total += power;
        }

        Some(total)
    }

    // 4. 術式の組み合わせ効果を計算
    fn calculate_combo_effect(&self, tech1: &str, tech2: &str) -> Option<(i32, String)> {
        let power1 = self.get_power(tech1)?;
        let power2 = self.get_power(tech2)?;

        let (combo_power, combo_name) = match (tech1, tech2) {
            ("蒼", "赫") | ("赫", "蒼") => {
                (power1 + power2 + 500, "虚式『茈』".to_string())
            },
            (a, b) if a == b => {
                (power1 * 2, format!("強化『{}』", a))
            },
            (a, b) => {
                let base_power = power1 + power2;
                (base_power + (base_power / 10), format!("{}×{}コンボ", a, b))
            },
        };

        Some((combo_power, combo_name))
    }

    // ボーナス: チェーン操作の実例
    fn get_technique_analysis(&self, name: &str) -> Option<String> {
        self.get_power(name)
            .and_then(|power| self.calculate_recommended_level(power))
            .map(|level| format!("{}は{}向けの術式です", name, level))
    }

    // 複雑なチェーン操作
    fn analyze_combo_viability(&self, tech1: &str, tech2: &str, user_level: i32) -> Option<String> {
        self.calculate_combo_effect(tech1, tech2)
            .and_then(|(power, name)| {
                if power <= user_level * 10 {
                    Some(format!("{}(威力: {})は実行可能です", name, power))
                } else {
                    Some(format!("{}(威力: {})は実行には危険すぎます", name, power))
                }
            })
    }
}

fn main() {
    let techniques = vec![
        TechniqueInfo { name: "蒼".to_string(), power: 1000, element: "無下限".to_string() },
        TechniqueInfo { name: "赫".to_string(), power: 1500, element: "無下限".to_string() },
        TechniqueInfo { name: "茈".to_string(), power: 3000, element: "無下限".to_string() },
        TechniqueInfo { name: "黒閃".to_string(), power: 800, element: "物理".to_string() },
    ];

    let db = TechniqueDatabase::new(techniques);

    println!("=== Option型チェーン操作システム ===");

    // 1. 基本的な威力取得
    let techniques_to_test = ["蒼", "赫", "存在しない術式"];
    for tech in techniques_to_test.iter() {
        match db.get_power(tech) {
            Some(power) => println!("✓ {}: 威力 {}", tech, power),
            None => println!("✗ {}: 見つかりません", tech),
        }
    }

    // 2. 推奨レベル計算のチェーン
    println!("\\n=== 術式分析 ===");
    for tech in ["蒼", "茈", "黒閃"].iter() {
        match db.get_technique_analysis(tech) {
            Some(analysis) => println!("✓ {}", analysis),
            None => println!("✗ {}の分析に失敗", tech),
        }
    }

    // 3. 合計威力計算
    println!("\\n=== 合計威力計算 ===");
    let combos = vec![
        vec!["蒼", "赫"],
        vec!["蒼", "赫", "茈"],
        vec!["蒼", "存在しない術式"],
    ];

    for combo in combos {
        match db.calculate_total_power(&combo) {
            Some(total) => {
                let level = db.calculate_recommended_level(total)
                    .unwrap_or("不明".to_string());
                println!("✓ {:?}: 合計威力 {} ({}レベル)", combo, total, level);
            },
            None => println!("✗ {:?}: 計算失敗(存在しない術式を含む)", combo),
        }
    }

    // 4. コンボ効果計算
    println!("\\n=== コンボ効果分析 ===");
    let combo_pairs = vec![
        ("蒼", "赫"),
        ("蒼", "蒼"),
        ("茈", "黒閃"),
    ];

    for (tech1, tech2) in combo_pairs {
        match db.calculate_combo_effect(tech1, tech2) {
            Some((power, name)) => {
                println!("✓ {} + {} = {} (威力: {})", tech1, tech2, name, power);
            },
            None => println!("✗ {} + {}: コンボ計算失敗", tech1, tech2),
        }
    }

    // 5. 複雑なチェーン操作
    println!("\\n=== コンボ実行可能性分析 ===");
    let user_levels = [100, 200, 300];

    for level in user_levels.iter() {
        println!("\\nユーザーレベル: {}", level);
        for (tech1, tech2) in [("蒼", "赫"), ("茈", "黒閃")].iter() {
            match db.analyze_combo_viability(tech1, tech2, *level) {
                Some(analysis) => println!("  {}", analysis),
                None => println!("  {}/{}: 分析失敗", tech1, tech2),
            }
        }
    }
}

中級編 - Result型とエラーハンドリング

問題3: 呪術師登録システム

バリデーション機能付きの呪術師登録システムをResult型を使って実装せよ。

#[derive(Debug)]
enum RegistrationError {
    InvalidName(String),
    InvalidPower(String),
    InvalidGrade(String),
    DuplicateEntry(String),
    SystemError(String),
}

#[derive(Debug, Clone)]
struct Sorcerer {
    name: String,
    power: i32,
    grade: String,
    techniques: Vec<String>,
}

struct RegistrationSystem {
    sorcerers: Vec<Sorcerer>,
}

impl RegistrationSystem {
    fn new() -> Self {
        // 実装
    }

    // 以下のメソッドを実装せよ:
    // 1. 名前のバリデーション
    // 2. 呪力のバリデーション
    // 3. 等級のバリデーション
    // 4. 重複チェック
    // 5. 呪術師の登録
    // 6. 術式の追加
}

fn main() {
    // テストケース
}
解答を見る
use std::fmt;

#[derive(Debug)]
enum RegistrationError {
    InvalidName(String),
    InvalidPower(String),
    InvalidGrade(String),
    DuplicateEntry(String),
    SystemError(String),
}

impl fmt::Display for RegistrationError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            RegistrationError::InvalidName(msg) => write!(f, "名前エラー: {}", msg),
            RegistrationError::InvalidPower(msg) => write!(f, "呪力エラー: {}", msg),
            RegistrationError::InvalidGrade(msg) => write!(f, "等級エラー: {}", msg),
            RegistrationError::DuplicateEntry(name) => write!(f, "重複エラー: {}は既に登録済みです", name),
            RegistrationError::SystemError(msg) => write!(f, "システムエラー: {}", msg),
        }
    }
}

impl std::error::Error for RegistrationError {}

#[derive(Debug, Clone)]
struct Sorcerer {
    name: String,
    power: i32,
    grade: String,
    techniques: Vec<String>,
}

impl Sorcerer {
    fn new(name: String, power: i32, grade: String) -> Self {
        Sorcerer {
            name,
            power,
            grade,
            techniques: Vec::new(),
        }
    }
}

struct RegistrationSystem {
    sorcerers: Vec<Sorcerer>,
}

impl RegistrationSystem {
    fn new() -> Self {
        RegistrationSystem {
            sorcerers: Vec::new(),
        }
    }

    // 1. 名前のバリデーション
    fn validate_name(&self, name: &str) -> Result<(), RegistrationError> {
        if name.is_empty() {
            return Err(RegistrationError::InvalidName("名前が空です".to_string()));
        }

        if name.len() < 2 {
            return Err(RegistrationError::InvalidName("名前は2文字以上である必要があります".to_string()));
        }

        if name.len() > 50 {
            return Err(RegistrationError::InvalidName("名前は50文字以下である必要があります".to_string()));
        }

        // 特殊文字チェック
        if name.chars().any(|c| c.is_ascii_digit()) {
            return Err(RegistrationError::InvalidName("名前に数字を含めることはできません".to_string()));
        }

        Ok(())
    }

    // 2. 呪力のバリデーション
    fn validate_power(&self, power: i32) -> Result<(), RegistrationError> {
        if power < 0 {
            return Err(RegistrationError::InvalidPower("呪力は0以上である必要があります".to_string()));
        }

        if power > 10000 {
            return Err(RegistrationError::InvalidPower("呪力は10000以下である必要があります".to_string()));
        }

        Ok(())
    }

    // 3. 等級のバリデーション
    fn validate_grade(&self, grade: &str) -> Result<(), RegistrationError> {
        let valid_grades = ["特級", "1級", "2級", "3級", "4級"];

        if !valid_grades.contains(&grade) {
            return Err(RegistrationError::InvalidGrade(
                format!("無効な等級: {}。有効な等級: {:?}", grade, valid_grades)
            ));
        }

        Ok(())
    }

    // 4. 重複チェック
    fn check_duplicate(&self, name: &str) -> Result<(), RegistrationError> {
        if self.sorcerers.iter().any(|s| s.name == name) {
            return Err(RegistrationError::DuplicateEntry(name.to_string()));
        }

        Ok(())
    }

    // 5. 呪術師の登録
    fn register_sorcerer(&mut self, name: String, power: i32, grade: String)
        -> Result<usize, RegistrationError> {

        // すべてのバリデーションを実行
        self.validate_name(&name)?;
        self.validate_power(power)?;
        self.validate_grade(&grade)?;
        self.check_duplicate(&name)?;

        // 等級と呪力の整合性チェック
        let min_power = match grade.as_str() {
            "特級" => 2000,
            "1級" => 1000,
            "2級" => 500,
            "3級" => 200,
            "4級" => 0,
            _ => return Err(RegistrationError::SystemError("想定外の等級".to_string())),
        };

        if power < min_power {
            return Err(RegistrationError::InvalidPower(
                format!("{}等級には最低{}の呪力が必要です", grade, min_power)
            ));
        }

        // 登録実行
        let sorcerer = Sorcerer::new(name, power, grade);
        self.sorcerers.push(sorcerer);

        Ok(self.sorcerers.len() - 1)  // インデックスを返す
    }

    // 6. 術式の追加
    fn add_technique(&mut self, sorcerer_index: usize, technique: String)
        -> Result<(), RegistrationError> {

        if technique.is_empty() {
            return Err(RegistrationError::SystemError("術式名が空です".to_string()));
        }

        let sorcerer = self.sorcerers.get_mut(sorcerer_index)
            .ok_or_else(|| RegistrationError::SystemError("無効な呪術師インデックス".to_string()))?;

        if sorcerer.techniques.contains(&technique) {
            return Err(RegistrationError::SystemError(
                format!("{}は既に{}を習得済みです", sorcerer.name, technique)
            ));
        }

        // 等級による技数制限
        let max_techniques = match sorcerer.grade.as_str() {
            "特級" => 10,
            "1級" => 5,
            "2級" => 3,
            "3級" => 2,
            "4級" => 1,
            _ => return Err(RegistrationError::SystemError("想定外の等級".to_string())),
        };

        if sorcerer.techniques.len() >= max_techniques {
            return Err(RegistrationError::SystemError(
                format!("{}等級は最大{}個の術式しか習得できません",
                        sorcerer.grade, max_techniques)
            ));
        }

        sorcerer.techniques.push(technique);
        Ok(())
    }

    // ボーナス: 一括登録
    fn batch_register(&mut self, registrations: Vec<(String, i32, String)>)
        -> Vec<Result<usize, RegistrationError>> {

        registrations.into_iter()
            .map(|(name, power, grade)| self.register_sorcerer(name, power, grade))
            .collect()
    }

    // 呪術師情報の取得
    fn get_sorcerer(&self, index: usize) -> Result<&Sorcerer, RegistrationError> {
        self.sorcerers.get(index)
            .ok_or_else(|| RegistrationError::SystemError("無効なインデックス".to_string()))
    }

    // 統計情報
    fn get_statistics(&self) -> Result<String, RegistrationError> {
        if self.sorcerers.is_empty() {
            return Err(RegistrationError::SystemError("登録された呪術師がいません".to_string()));
        }

        let total = self.sorcerers.len();
        let avg_power = self.sorcerers.iter().map(|s| s.power).sum::<i32>() / total as i32;

        let mut grade_counts = std::collections::HashMap::new();
        for sorcerer in &self.sorcerers {
            *grade_counts.entry(&sorcerer.grade).or_insert(0) += 1;
        }

        let mut stats = format!("登録呪術師数: {}\\n平均呪力: {}\\n\\n等級分布:\\n", total, avg_power);
        for (grade, count) in grade_counts {
            stats.push_str(&format!("{}: {}人\\n", grade, count));
        }

        Ok(stats)
    }
}

fn main() {
    let mut system = RegistrationSystem::new();

    println!("=== 呪術師登録システム ===");

    // 正常ケース
    let valid_registrations = vec![
        ("五条悟".to_string(), 3000, "特級".to_string()),
        ("虎杖悠仁".to_string(), 1200, "1級".to_string()),
        ("伏黒恵".to_string(), 800, "2級".to_string()),
    ];

    for (name, power, grade) in valid_registrations {
        match system.register_sorcerer(name.clone(), power, grade) {
            Ok(index) => {
                println!("✓ {}を登録しました (ID: {})", name, index);

                // 術式の追加テスト
                let techniques = match name.as_str() {
                    "五条悟" => vec!["無下限呪術", "術式順転『蒼』"],
                    "虎杖悠仁" => vec!["黒閃"],
                    "伏黒恵" => vec!["十種影法術"],
                    _ => vec![],
                };

                for technique in techniques {
                    match system.add_technique(index, technique.to_string()) {
                        Ok(_) => println!("  ✓ {}を習得", technique),
                        Err(e) => println!("  ✗ 術式追加エラー: {}", e),
                    }
                }
            },
            Err(error) => println!("✗ {}の登録失敗: {}", name, error),
        }
    }

    // エラーケース
    println!("\\n=== エラーケーステスト ===");

    let error_cases = vec![
        ("".to_string(), 1000, "1級".to_string()),           // 空の名前
        ("テスト1".to_string(), 1000, "1級".to_string()),      // 数字を含む名前
        ("テスト".to_string(), -100, "1級".to_string()),       // 負の呪力
        ("テスト".to_string(), 1000, "無効等級".to_string()),   // 無効な等級
        ("五条悟".to_string(), 2000, "特級".to_string()),      // 重複登録
        ("弱い特級".to_string(), 500, "特級".to_string()),     // 等級と呪力の不整合
    ];

    for (name, power, grade) in error_cases {
        match system.register_sorcerer(name.clone(), power, grade.clone()) {
            Ok(index) => println!("✓ {}を登録しました (ID: {})", name, index),
            Err(error) => println!("✗ 予期されたエラー: {}", error),
        }
    }

    // 一括登録テスト
    println!("\\n=== 一括登録テスト ===");
    let batch_registrations = vec![
        ("新人A".to_string(), 300, "3級".to_string()),
        ("新人B".to_string(), 600, "2級".to_string()),
        ("".to_string(), 1000, "1級".to_string()),  // エラーケース
    ];

    let results = system.batch_register(batch_registrations);
    for (i, result) in results.iter().enumerate() {
        match result {
            Ok(index) => println!("✓ 一括登録 {}: 成功 (ID: {})", i, index),
            Err(error) => println!("✗ 一括登録 {}: {}", i, error),
        }
    }

    // 統計情報
    println!("\\n=== 統計情報 ===");
    match system.get_statistics() {
        Ok(stats) => println!("{}", stats),
        Err(error) => println!("統計エラー: {}", error),
    }
}

問題4: ファイルI/Oとエラーハンドリング

呪術師データをファイルに保存・読み込みするシステムを実装せよ。複数のエラー型を統合したカスタムエラーを使用すること。

use std::fs;
use std::io;

// カスタムエラー型を定義し、以下の機能を実装せよ:
// 1. ファイルからの呪術師データ読み込み
// 2. ファイルへの呪術師データ保存
// 3. バックアップ機能
// 4. データの整合性チェック
// 5. 複数ファイルの一括処理

fn main() {
    // テスト用コード
}
解答を見る
use std::fmt;
use std::fs;
use std::io;

#[derive(Debug)]
enum FileSystemError {
    IoError(io::Error),
    ParseError(String),
    ValidationError(String),
    CorruptedData(String),
    BackupError(String),
}

impl fmt::Display for FileSystemError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            FileSystemError::IoError(err) => write!(f, "I/Oエラー: {}", err),
            FileSystemError::ParseError(msg) => write!(f, "パースエラー: {}", msg),
            FileSystemError::ValidationError(msg) => write!(f, "バリデーションエラー: {}", msg),
            FileSystemError::CorruptedData(msg) => write!(f, "データ破損: {}", msg),
            FileSystemError::BackupError(msg) => write!(f, "バックアップエラー: {}", msg),
        }
    }
}

impl std::error::Error for FileSystemError {}

impl From<io::Error> for FileSystemError {
    fn from(err: io::Error) -> Self {
        FileSystemError::IoError(err)
    }
}

#[derive(Debug, Clone)]
struct Sorcerer {
    id: u32,
    name: String,
    power: i32,
    grade: String,
    techniques: Vec<String>,
}

impl Sorcerer {
    fn new(id: u32, name: String, power: i32, grade: String) -> Self {
        Sorcerer {
            id,
            name,
            power,
            grade,
            techniques: Vec::new(),
        }
    }

    // CSV形式での文字列化
    fn to_csv_line(&self) -> String {
        format!("{},{},{},{},{}",
                self.id,
                self.name,
                self.power,
                self.grade,
                self.techniques.join(";"))
    }

    // CSV行からの復元
    fn from_csv_line(line: &str) -> Result<Self, FileSystemError> {
        let parts: Vec<&str> = line.split(',').collect();

        if parts.len() != 5 {
            return Err(FileSystemError::ParseError(
                format!("無効なCSV形式: {}", line)
            ));
        }

        let id: u32 = parts[0].parse()
            .map_err(|_| FileSystemError::ParseError("IDのパースに失敗".to_string()))?;

        let power: i32 = parts[2].parse()
            .map_err(|_| FileSystemError::ParseError("呪力のパースに失敗".to_string()))?;

        let techniques = if parts[4].is_empty() {
            Vec::new()
        } else {
            parts[4].split(';').map(|s| s.to_string()).collect()
        };

        let mut sorcerer = Sorcerer::new(id, parts[1].to_string(), power, parts[3].to_string());
        sorcerer.techniques = techniques;

        Ok(sorcerer)
    }

    // データの整合性チェック
    fn validate(&self) -> Result<(), FileSystemError> {
        if self.name.is_empty() {
            return Err(FileSystemError::ValidationError("名前が空です".to_string()));
        }

        if self.power < 0 {
            return Err(FileSystemError::ValidationError("呪力が負の値です".to_string()));
        }

        let valid_grades = ["特級", "1級", "2級", "3級", "4級"];
        if !valid_grades.contains(&self.grade.as_str()) {
            return Err(FileSystemError::ValidationError(
                format!("無効な等級: {}", self.grade)
            ));
        }

        Ok(())
    }
}

struct SorcererFileManager {
    filename: String,
    backup_dir: String,
}

impl SorcererFileManager {
    fn new(filename: &str, backup_dir: &str) -> Self {
        SorcererFileManager {
            filename: filename.to_string(),
            backup_dir: backup_dir.to_string(),
        }
    }

    // 1. ファイルからの読み込み
    fn load_sorcerers(&self) -> Result<Vec<Sorcerer>, FileSystemError> {
        let content = fs::read_to_string(&self.filename)?;

        let mut sorcerers = Vec::new();
        let mut line_number = 0;

        for line in content.lines() {
            line_number += 1;

            if line.trim().is_empty() {
                continue;
            }

            let sorcerer = Sorcerer::from_csv_line(line)
                .map_err(|e| FileSystemError::ParseError(
                    format!("行{}: {}", line_number, e)
                ))?;

            sorcerer.validate()
                .map_err(|e| FileSystemError::CorruptedData(
                    format!("行{}のデータが無効: {}", line_number, e)
                ))?;

            sorcerers.push(sorcerer);
        }

        // 重複IDチェック
        self.check_duplicate_ids(&sorcerers)?;

        Ok(sorcerers)
    }

    // 2. ファイルへの保存
    fn save_sorcerers(&self, sorcerers: &[Sorcerer]) -> Result<(), FileSystemError> {
        // 保存前にバリデーション
        for sorcerer in sorcerers {
            sorcerer.validate()?;
        }

        self.check_duplicate_ids(sorcerers)?;

        // CSV形式で保存
        let mut content = String::new();
        content.push_str("# 呪術師データベース\\n");
        content.push_str("# ID,名前,呪力,等級,術式(;区切り)\\n");

        for sorcerer in sorcerers {
            content.push_str(&sorcerer.to_csv_line());
            content.push('\\n');
        }

        fs::write(&self.filename, content)?;
        Ok(())
    }

    // 3. バックアップ機能
    fn create_backup(&self) -> Result<String, FileSystemError> {
        // バックアップディレクトリの作成
        fs::create_dir_all(&self.backup_dir)
            .map_err(|e| FileSystemError::BackupError(format!("ディレクトリ作成失敗: {}", e)))?;

        // タイムスタンプ付きバックアップファイル名
        let timestamp = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|e| FileSystemError::BackupError(format!("タイムスタンプ取得失敗: {}", e)))?
            .as_secs();

        let backup_filename = format!("{}/sorcerers_backup_{}.csv", self.backup_dir, timestamp);

        // ファイルをバックアップ
        let content = fs::read_to_string(&self.filename)
            .map_err(|e| FileSystemError::BackupError(format!("元ファイル読み込み失敗: {}", e)))?;

        fs::write(&backup_filename, content)
            .map_err(|e| FileSystemError::BackupError(format!("バックアップ書き込み失敗: {}", e)))?;

        Ok(backup_filename)
    }

    fn restore_from_backup(&self, backup_filename: &str) -> Result<(), FileSystemError> {
        let content = fs::read_to_string(backup_filename)
            .map_err(|e| FileSystemError::BackupError(format!("バックアップ読み込み失敗: {}", e)))?;

        // バックアップデータの検証
        let _sorcerers = self.parse_content(&content)?;

        fs::write(&self.filename, content)
            .map_err(|e| FileSystemError::BackupError(format!("復元書き込み失敗: {}", e)))?;

        Ok(())
    }

    // 4. データの整合性チェック
    fn verify_data_integrity(&self) -> Result<String, FileSystemError> {
        let sorcerers = self.load_sorcerers()?;

        let mut report = String::new();
        report.push_str("=== データ整合性レポート ===\\n");
        report.push_str(&format!("総レコード数: {}\\n", sorcerers.len()));

        // 等級分布
        let mut grade_counts = std::collections::HashMap::new();
        for sorcerer in &sorcerers {
            *grade_counts.entry(&sorcerer.grade).or_insert(0) += 1;
        }

        report.push_str("\\n等級分布:\\n");
        for (grade, count) in grade_counts {
            report.push_str(&format!("  {}: {}人\\n", grade, count));
        }

        // 呪力統計
        let powers: Vec<i32> = sorcerers.iter().map(|s| s.power).collect();
        let avg_power = powers.iter().sum::<i32>() / powers.len() as i32;
        let max_power = powers.iter().max().unwrap_or(&0);
        let min_power = powers.iter().min().unwrap_or(&0);

        report.push_str(&format!("\\n呪力統計:\\n"));
        report.push_str(&format!("  平均: {}\\n", avg_power));
        report.push_str(&format!("  最大: {}\\n", max_power));
        report.push_str(&format!("  最小: {}\\n", min_power));

        // 異常データのチェック
        let mut anomalies = Vec::new();
        for sorcerer in &sorcerers {
            let expected_min_power = match sorcerer.grade.as_str() {
                "特級" => 2000,
                "1級" => 1000,
                "2級" => 500,
                "3級" => 200,
                "4級" => 0,
                _ => 0,
            };

            if sorcerer.power < expected_min_power {
                anomalies.push(format!("{}(ID:{}): {}等級なのに呪力が{}しかない",
                    sorcerer.name, sorcerer.id, sorcerer.grade, sorcerer.power));
            }
        }

        if !anomalies.is_empty() {
            report.push_str("\\n⚠️ 異常データ:\\n");
            for anomaly in anomalies {
                report.push_str(&format!("  {}", anomaly));
            }
        } else {
            report.push_str("\\n✅ 異常データは見つかりませんでした\\n");
        }

        Ok(report)
    }

    // 5. 複数ファイルの一括処理
    fn batch_process_files(&self, file_patterns: &[&str]) -> Result<Vec<String>, FileSystemError> {
        let mut results = Vec::new();

        for pattern in file_patterns {
            let files = glob::glob(pattern)
                .map_err(|e| FileSystemError::IoError(
                    io::Error::new(io::ErrorKind::InvalidInput, format!("Globパターンエラー: {}", e))
                ))?;

            for file_result in files {
                let file_path = file_result
                    .map_err(|e| FileSystemError::IoError(
                        io::Error::new(io::ErrorKind::NotFound, format!("ファイルパスエラー: {}", e))
                    ))?;

                let temp_manager = SorcererFileManager::new(
                    file_path.to_str().unwrap_or("unknown"),
                    &self.backup_dir
                );

                match temp_manager.verify_data_integrity() {
                    Ok(report) => {
                        results.push(format!("✅ {}: 整合性チェック完了",
                                           file_path.display()));
                    },
                    Err(e) => {
                        results.push(format!("❌ {}: {}",
                                           file_path.display(), e));
                    }
                }
            }
        }

        Ok(results)
    }

    // ヘルパーメソッド
    fn check_duplicate_ids(&self, sorcerers: &[Sorcerer]) -> Result<(), FileSystemError> {
        let mut seen_ids = std::collections::HashSet::new();

        for sorcerer in sorcerers {
            if !seen_ids.insert(sorcerer.id) {
                return Err(FileSystemError::CorruptedData(
                    format!("重複したID: {}", sorcerer.id)
                ));
            }
        }

        Ok(())
    }

    fn parse_content(&self, content: &str) -> Result<Vec<Sorcerer>, FileSystemError> {
        let mut sorcerers = Vec::new();

        for line in content.lines() {
            if line.trim().is_empty() || line.starts_with('#') {
                continue;
            }

            let sorcerer = Sorcerer::from_csv_line(line)?;
            sorcerer.validate()?;
            sorcerers.push(sorcerer);
        }

        Ok(sorcerers)
    }
}

fn main() {
    let manager = SorcererFileManager::new("sorcerers.csv", "backups");

    println!("=== 呪術師ファイル管理システム ===");

    // テスト用データの作成
    let mut test_sorcerers = vec![
        Sorcerer::new(1, "五条悟".to_string(), 3000, "特級".to_string()),
        Sorcerer::new(2, "虎杖悠仁".to_string(), 1200, "1級".to_string()),
        Sorcerer::new(3, "伏黒恵".to_string(), 800, "2級".to_string()),
    ];

    // 術式の追加
    test_sorcerers[0].techniques.push("無下限呪術".to_string());
    test_sorcerers[0].techniques.push("術式順転『蒼』".to_string());
    test_sorcerers[1].techniques.push("黒閃".to_string());
    test_sorcerers[2].techniques.push("十種影法術".to_string());

    // 1. データの保存
    match manager.save_sorcerers(&test_sorcerers) {
        Ok(_) => println!("✅ データ保存完了"),
        Err(e) => println!("❌ 保存エラー: {}", e),
    }

    // 2. データの読み込み
    match manager.load_sorcerers() {
        Ok(sorcerers) => {
            println!("✅ データ読み込み完了: {}件", sorcerers.len());
            for sorcerer in &sorcerers[..2] {  // 最初の2件のみ表示
                println!("  {} (ID:{}, 呪力:{}, 等級:{})",
                         sorcerer.name, sorcerer.id, sorcerer.power, sorcerer.grade);
            }
        },
        Err(e) => println!("❌ 読み込みエラー: {}", e),
    }

    // 3. バックアップの作成
    match manager.create_backup() {
        Ok(backup_file) => println!("✅ バックアップ作成: {}", backup_file),
        Err(e) => println!("❌ バックアップエラー: {}", e),
    }

    // 4. データ整合性チェック
    match manager.verify_data_integrity() {
        Ok(report) => println!("\\n{}", report),
        Err(e) => println!("❌ 整合性チェックエラー: {}", e),
    }

    // 5. エラーケースのテスト
    println!("\\n=== エラーケーステスト ===");

    // 不正なデータでテスト
    let invalid_content = "1,五条悟,3000,特級,無下限呪術\\ninvalid,line,format\\n";
    if let Err(e) = fs::write("invalid_test.csv", invalid_content) {
        println!("テストファイル作成エラー: {}", e);
    } else {
        let invalid_manager = SorcererFileManager::new("invalid_test.csv", "backups");
        match invalid_manager.load_sorcerers() {
            Ok(_) => println!("❌ 無効データが読み込まれてしまいました"),
            Err(e) => println!("✅ 予期されたエラー: {}", e),
        }

        // テストファイルの削除
        let _ = fs::remove_file("invalid_test.csv");
    }

    println!("\\n✅ ファイル管理システムテスト完了");
}

// 注意: このコードを実行するには Cargo.toml に以下を追加してください:
// [dependencies]
// glob = "0.3"

上級編 - 高度なエラーハンドリングパターン

問題5: マルチスレッド対応エラーハンドリング

複数のスレッドで並行処理を行い、各スレッドのエラーを適切に集約するシステムを実装せよ。

use std::sync::{Arc, Mutex};
use std::thread;

// マルチスレッドでの呪術師データ処理システム
// 要件:
// 1. 複数スレッドでデータを並行処理
// 2. 各スレッドのエラーを収集
// 3. 処理結果とエラーの統合
// 4. 安全なリソース共有

fn main() {
    // 実装
}
解答を見る
use std::sync::{Arc, Mutex, mpsc};
use std::thread;
use std::time::Duration;

#[derive(Debug, Clone)]
enum ProcessingError {
    InvalidData(String),
    CalculationError(String),
    ThreadError(String),
    TimeoutError,
}

impl std::fmt::Display for ProcessingError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match self {
            ProcessingError::InvalidData(msg) => write!(f, "データエラー: {}", msg),
            ProcessingError::CalculationError(msg) => write!(f, "計算エラー: {}", msg),
            ProcessingError::ThreadError(msg) => write!(f, "スレッドエラー: {}", msg),
            ProcessingError::TimeoutError => write!(f, "タイムアウトエラー"),
        }
    }
}

impl std::error::Error for ProcessingError {}

#[derive(Debug, Clone)]
struct SorcererData {
    id: u32,
    name: String,
    power: i32,
    techniques: Vec<String>,
}

impl SorcererData {
    fn new(id: u32, name: String, power: i32) -> Self {
        SorcererData {
            id,
            name,
            power,
            techniques: Vec::new(),
        }
    }

    // 複雑な計算処理をシミュレート
    fn calculate_combat_rating(&self) -> Result<f64, ProcessingError> {
        if self.power <= 0 {
            return Err(ProcessingError::InvalidData(
                format!("無効な呪力: {}", self.power)
            ));
        }

        // 意図的に時間のかかる処理
        thread::sleep(Duration::from_millis(100));

        let base_rating = self.power as f64;
        let technique_bonus = self.techniques.len() as f64 * 100.0;

        // 意図的なエラーケース
        if self.name.contains("エラー") {
            return Err(ProcessingError::CalculationError(
                "計算中にエラーが発生しました".to_string()
            ));
        }

        let rating = base_rating + technique_bonus;

        if rating > 10000.0 {
            Err(ProcessingError::CalculationError(
                "戦闘力が上限を超えました".to_string()
            ))
        } else {
            Ok(rating)
        }
    }
}

#[derive(Debug)]
struct ProcessingResult {
    sorcerer_id: u32,
    result: Result<f64, ProcessingError>,
    thread_id: thread::ThreadId,
}

struct ParallelProcessor {
    thread_count: usize,
    timeout_ms: u64,
}

impl ParallelProcessor {
    fn new(thread_count: usize, timeout_ms: u64) -> Self {
        ParallelProcessor {
            thread_count,
            timeout_ms,
        }
    }

    fn process_sorcerers(&self, sorcerers: Vec<SorcererData>)
        -> Result<Vec<ProcessingResult>, ProcessingError> {

        let sorcerers = Arc::new(Mutex::new(sorcerers));
        let (tx, rx) = mpsc::channel();
        let mut handles = Vec::new();

        // ワーカースレッドの起動
        for worker_id in 0..self.thread_count {
            let sorcerers_clone = Arc::clone(&sorcerers);
            let tx_clone = tx.clone();

            let handle = thread::spawn(move || {
                let thread_id = thread::current().id();

                loop {
                    // 次の処理対象を取得
                    let sorcerer = {
                        let mut sorcerers_guard = sorcerers_clone.lock()
                            .map_err(|_| ProcessingError::ThreadError("ロック取得失敗".to_string()))?;

                        sorcerers_guard.pop()
                    };

                    match sorcerer {
                        Some(sorcerer) => {
                            let sorcerer_id = sorcerer.id;
                            let result = sorcerer.calculate_combat_rating();

                            let processing_result = ProcessingResult {
                                sorcerer_id,
                                result,
                                thread_id,
                            };

                            if tx_clone.send(processing_result).is_err() {
                                break; // チャンネルが閉じられた
                            }
                        },
                        None => break, // 処理する項目がない
                    }
                }

                Ok::<(), ProcessingError>(())
            });

            handles.push(handle);
        }

        // 送信者のクローンを削除(受信者が終了を検知できるように)
        drop(tx);

        // 結果の収集
        let mut results = Vec::new();
        let start_time = std::time::Instant::now();

        while let Ok(result) = rx.recv() {
            results.push(result);

            // タイムアウトチェック
            if start_time.elapsed().as_millis() > self.timeout_ms as u128 {
                return Err(ProcessingError::TimeoutError);
            }
        }

        // スレッドの終了を待機
        for handle in handles {
            match handle.join() {
                Ok(Ok(())) => {},
                Ok(Err(e)) => return Err(e),
                Err(_) => return Err(ProcessingError::ThreadError("スレッド終了エラー".to_string())),
            }
        }

        Ok(results)
    }

    fn analyze_results(&self, results: &[ProcessingResult]) -> ProcessingSummary {
        let mut successful = 0;
        let mut failed = 0;
        let mut total_rating = 0.0;
        let mut errors_by_type = std::collections::HashMap::new();
        let mut results_by_thread = std::collections::HashMap::new();

        for result in results {
            // スレッド別統計
            let thread_stats = results_by_thread.entry(result.thread_id).or_insert((0, 0));

            match &result.result {
                Ok(rating) => {
                    successful += 1;
                    total_rating += rating;
                    thread_stats.0 += 1;
                },
                Err(error) => {
                    failed += 1;
                    thread_stats.1 += 1;

                    let error_type = match error {
                        ProcessingError::InvalidData(_) => "InvalidData",
                        ProcessingError::CalculationError(_) => "CalculationError",
                        ProcessingError::ThreadError(_) => "ThreadError",
                        ProcessingError::TimeoutError => "TimeoutError",
                    };

                    *errors_by_type.entry(error_type).or_insert(0) += 1;
                },
            }
        }

        ProcessingSummary {
            total_processed: results.len(),
            successful,
            failed,
            average_rating: if successful > 0 { total_rating / successful as f64 } else { 0.0 },
            errors_by_type,
            results_by_thread,
        }
    }
}

#[derive(Debug)]
struct ProcessingSummary {
    total_processed: usize,
    successful: usize,
    failed: usize,
    average_rating: f64,
    errors_by_type: std::collections::HashMap<&'static str, i32>,
    results_by_thread: std::collections::HashMap<thread::ThreadId, (i32, i32)>, // (成功数, 失敗数)
}

impl ProcessingSummary {
    fn generate_report(&self) -> String {
        let mut report = String::new();

        report.push_str("=== 並行処理結果レポート ===\\n");
        report.push_str(&format!("総処理数: {}\\n", self.total_processed));
        report.push_str(&format!("成功: {} ({:.1}%)\\n",
            self.successful,
            (self.successful as f64 / self.total_processed as f64) * 100.0));
        report.push_str(&format!("失敗: {} ({:.1}%)\\n",
            self.failed,
            (self.failed as f64 / self.total_processed as f64) * 100.0));

        if self.successful > 0 {
            report.push_str(&format!("平均戦闘力: {:.1}\\n", self.average_rating));
        }

        if !self.errors_by_type.is_empty() {
            report.push_str("\\nエラー種別:\\n");
            for (error_type, count) in &self.errors_by_type {
                report.push_str(&format!("  {}: {}件\\n", error_type, count));
            }
        }

        report.push_str(&format!("\\nスレッド別処理結果:\\n"));
        for (thread_id, (success, failure)) in &self.results_by_thread {
            report.push_str(&format!("  {:?}: 成功{}件, 失敗{}件\\n",
                thread_id, success, failure));
        }

        report
    }
}

fn create_test_data() -> Vec<SorcererData> {
    let mut sorcerers = vec![
        SorcererData::new(1, "五条悟".to_string(), 3000),
        SorcererData::new(2, "両面宿儺".to_string(), 2800),
        SorcererData::new(3, "虎杖悠仁".to_string(), 1200),
        SorcererData::new(4, "伏黒恵".to_string(), 1000),
        SorcererData::new(5, "釘崎野薔薇".to_string(), 900),
        SorcererData::new(6, "禪院真希".to_string(), 800),
        SorcererData::new(7, "狗巻棘".to_string(), 700),
        SorcererData::new(8, "パンダ".to_string(), 600),
        SorcererData::new(9, "エラーテスト".to_string(), 1000), // 意図的なエラー
        SorcererData::new(10, "無効呪力".to_string(), -100),     // 無効データ
    ];

    // 術式の追加
    sorcerers[0].techniques.extend(vec!["無下限呪術".to_string(), "領域展開".to_string()]);
    sorcerers[1].techniques.extend(vec!["解".to_string(), "捌".to_string()]);
    sorcerers[2].techniques.push("黒閃".to_string());
    sorcerers[3].techniques.push("十種影法術".to_string());

    // 追加のテストデータ
    for i in 11..=20 {
        let mut sorcerer = SorcererData::new(i, format!("呪術師{}", i), 500 + (i as i32 * 50));
        sorcerer.techniques.push(format!("術式{}", i));
        sorcerers.push(sorcerer);
    }

    sorcerers
}

fn main() {
    println!("=== マルチスレッド呪術師処理システム ===");

    let processor = ParallelProcessor::new(4, 5000); // 4スレッド、5秒タイムアウト
    let test_data = create_test_data();

    println!("処理対象: {}件の呪術師データ", test_data.len());
    println!("スレッド数: {}", processor.thread_count);
    println!("タイムアウト: {}ms\\n", processor.timeout_ms);

    let start_time = std::time::Instant::now();

    match processor.process_sorcerers(test_data) {
        Ok(results) => {
            let elapsed = start_time.elapsed();
            println!("✅ 処理完了(実行時間: {:.2}秒)\\n", elapsed.as_secs_f64());

            // 結果の詳細表示(最初の5件)
            println!("=== 処理結果詳細(最初の5件)===");
            for (i, result) in results.iter().take(5).enumerate() {
                match &result.result {
                    Ok(rating) => {
                        println!("{}. ID {}: 戦闘力 {:.1} (スレッド: {:?})",
                                 i + 1, result.sorcerer_id, rating, result.thread_id);
                    },
                    Err(error) => {
                        println!("{}. ID {}: エラー - {} (スレッド: {:?})",
                                 i + 1, result.sorcerer_id, error, result.thread_id);
                    }
                }
            }

            // 統計分析
            let summary = processor.analyze_results(&results);
            println!("\\n{}", summary.generate_report());

        },
        Err(error) => {
            println!("❌ 処理失敗: {}", error);
        }
    }

    // パフォーマンステスト
    println!("\\n=== パフォーマンステスト ===");

    let large_dataset: Vec<SorcererData> = (1..=1000)
        .map(|i| {
            let mut sorcerer = SorcererData::new(i, format!("呪術師{}", i), 500 + (i % 1000));
            sorcerer.techniques.push(format!("術式{}", i));
            sorcerer
        })
        .collect();

    println!("大規模データセット: {}件", large_dataset.len());

    let large_processor = ParallelProcessor::new(8, 10000); // 8スレッド、10秒タイムアウト
    let start_time = std::time::Instant::now();

    match large_processor.process_sorcerers(large_dataset) {
        Ok(results) => {
            let elapsed = start_time.elapsed();
            println!("✅ 大規模処理完了(実行時間: {:.2}秒)", elapsed.as_secs_f64());

            let summary = large_processor.analyze_results(&results);
            println!("\\n{}", summary.generate_report());
        },
        Err(error) => {
            println!("❌ 大規模処理失敗: {}", error);
        }
    }
}

総合問題

問題6: 呪術師管理マイクロサービス

これまで学んだすべての要素を統合して、マイクロサービス風の呪術師管理システムを実装せよ。

要件:

  • RESTful API風のインターフェース
  • 複数のサービス間でのエラー伝播
  • 設定管理とバリデーション
  • ログ記録システム
  • リトライ機構

自由に設計して実装してみよう!すべてのエラーハンドリング技術を駆使すること。

解答例を見る
use std::collections::HashMap;
use std::fmt;
use std::time::{Duration, Instant};

// サービス層のエラー型
#[derive(Debug)]
enum ServiceError {
    ValidationError(String),
    NotFound(String),
    Conflict(String),
    InternalError(String),
    ExternalServiceError(String),
    TimeoutError,
    RateLimitExceeded,
}

impl fmt::Display for ServiceError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ServiceError::ValidationError(msg) => write!(f, "[400] Validation: {}", msg),
            ServiceError::NotFound(resource) => write!(f, "[404] Not Found: {}", resource),
            ServiceError::Conflict(msg) => write!(f, "[409] Conflict: {}", msg),
            ServiceError::InternalError(msg) => write!(f, "[500] Internal: {}", msg),
            ServiceError::ExternalServiceError(msg) => write!(f, "[502] External: {}", msg),
            ServiceError::TimeoutError => write!(f, "[504] Timeout"),
            ServiceError::RateLimitExceeded => write!(f, "[429] Rate Limit Exceeded"),
        }
    }
}

impl std::error::Error for ServiceError {}

// APIレスポンス型
#[derive(Debug)]
struct ApiResponse<T> {
    success: bool,
    data: Option<T>,
    error: Option<String>,
    timestamp: String,
    request_id: String,
}

impl<T> ApiResponse<T> {
    fn success(data: T, request_id: String) -> Self {
        ApiResponse {
            success: true,
            data: Some(data),
            error: None,
            timestamp: chrono::Utc::now().to_rfc3339(),
            request_id,
        }
    }

    fn error(error: ServiceError, request_id: String) -> ApiResponse<()> {
        ApiResponse {
            success: false,
            data: None,
            error: Some(error.to_string()),
            timestamp: chrono::Utc::now().to_rfc3339(),
            request_id,
        }
    }
}

// 設定管理
#[derive(Debug, Clone)]
struct ServiceConfig {
    max_retry_attempts: u32,
    retry_delay_ms: u64,
    timeout_ms: u64,
    rate_limit_per_minute: u32,
    log_level: String,
}

impl Default for ServiceConfig {
    fn default() -> Self {
        ServiceConfig {
            max_retry_attempts: 3,
            retry_delay_ms: 1000,
            timeout_ms: 5000,
            rate_limit_per_minute: 100,
            log_level: "INFO".to_string(),
        }
    }
}

// ログ記録システム
struct Logger {
    level: String,
}

impl Logger {
    fn new(level: String) -> Self {
        Logger { level }
    }

    fn info(&self, message: &str, context: &HashMap<String, String>) {
        println!("[INFO] {} | {:?}", message, context);
    }

    fn warn(&self, message: &str, context: &HashMap<String, String>) {
        println!("[WARN] {} | {:?}", message, context);
    }

    fn error(&self, message: &str, error: &dyn std::error::Error, context: &HashMap<String, String>) {
        println!("[ERROR] {} | Error: {} | {:?}", message, error, context);
    }
}

// レート制限管理
struct RateLimiter {
    requests: HashMap<String, Vec<Instant>>,
    limit_per_minute: u32,
}

impl RateLimiter {
    fn new(limit_per_minute: u32) -> Self {
        RateLimiter {
            requests: HashMap::new(),
            limit_per_minute,
        }
    }

    fn check_rate_limit(&mut self, client_id: &str) -> Result<(), ServiceError> {
        let now = Instant::now();
        let minute_ago = now - Duration::from_secs(60);

        let requests = self.requests.entry(client_id.to_string()).or_insert_with(Vec::new);

        // 1分以上前のリクエストを削除
        requests.retain(|&time| time > minute_ago);

        if requests.len() >= self.limit_per_minute as usize {
            return Err(ServiceError::RateLimitExceeded);
        }

        requests.push(now);
        Ok(())
    }
}

// リトライ機構
struct RetryPolicy {
    max_attempts: u32,
    delay_ms: u64,
}

impl RetryPolicy {
    fn new(max_attempts: u32, delay_ms: u64) -> Self {
        RetryPolicy { max_attempts, delay_ms }
    }

    fn execute<T, F>(&self, operation: F) -> Result<T, ServiceError>
    where
        F: Fn() -> Result<T, ServiceError>,
    {
        let mut last_error = ServiceError::InternalError("No attempts made".to_string());

        for attempt in 1..=self.max_attempts {
            match operation() {
                Ok(result) => return Ok(result),
                Err(error) => {
                    last_error = error;
                    if attempt < self.max_attempts {
                        std::thread::sleep(Duration::from_millis(self.delay_ms * attempt as u64));
                    }
                }
            }
        }

        Err(last_error)
    }
}

// 呪術師エンティティ
#[derive(Debug, Clone)]
struct Sorcerer {
    id: String,
    name: String,
    power: i32,
    grade: String,
    techniques: Vec<String>,
    active: bool,
    created_at: String,
    updated_at: String,
}

impl Sorcerer {
    fn new(id: String, name: String, power: i32, grade: String) -> Result<Self, ServiceError> {
        if name.is_empty() {
            return Err(ServiceError::ValidationError("名前は必須です".to_string()));
        }

        let valid_grades = ["特級", "1級", "2級", "3級", "4級"];
        if !valid_grades.contains(&grade.as_str()) {
            return Err(ServiceError::ValidationError(format!("無効な等級: {}", grade)));
        }

        if power < 0 || power > 10000 {
            return Err(ServiceError::ValidationError("呪力は0-10000の範囲である必要があります".to_string()));
        }

        let now = chrono::Utc::now().to_rfc3339();

        Ok(Sorcerer {
            id,
            name,
            power,
            grade,
            techniques: Vec::new(),
            active: true,
            created_at: now.clone(),
            updated_at: now,
        })
    }

    fn add_technique(&mut self, technique: String) -> Result<(), ServiceError> {
        if technique.is_empty() {
            return Err(ServiceError::ValidationError("術式名は必須です".to_string()));
        }

        if self.techniques.contains(&technique) {
            return Err(ServiceError::Conflict(format!("術式'{}'は既に習得済みです", technique)));
        }

        self.techniques.push(technique);
        self.updated_at = chrono::Utc::now().to_rfc3339();
        Ok(())
    }
}

// データストア(簡易版)
struct SorcererStore {
    data: HashMap<String, Sorcerer>,
    next_id: u32,
}

impl SorcererStore {
    fn new() -> Self {
        SorcererStore {
            data: HashMap::new(),
            next_id: 1,
        }
    }

    fn create(&mut self, name: String, power: i32, grade: String) -> Result<Sorcerer, ServiceError> {
        let id = format!("sorcerer_{}", self.next_id);
        self.next_id += 1;

        // 重複チェック
        if self.data.values().any(|s| s.name == name) {
            return Err(ServiceError::Conflict(format!("名前'{}'は既に使用されています", name)));
        }

        let sorcerer = Sorcerer::new(id.clone(), name, power, grade)?;
        self.data.insert(id.clone(), sorcerer.clone());

        Ok(sorcerer)
    }

    fn get(&self, id: &str) -> Result<&Sorcerer, ServiceError> {
        self.data.get(id)
            .ok_or_else(|| ServiceError::NotFound(format!("呪術師ID: {}", id)))
    }

    fn get_mut(&mut self, id: &str) -> Result<&mut Sorcerer, ServiceError> {
        self.data.get_mut(id)
            .ok_or_else(|| ServiceError::NotFound(format!("呪術師ID: {}", id)))
    }

    fn list(&self) -> Vec<&Sorcerer> {
        self.data.values().filter(|s| s.active).collect()
    }

    fn update(&mut self, id: &str, update_fn: impl FnOnce(&mut Sorcerer) -> Result<(), ServiceError>)
        -> Result<&Sorcerer, ServiceError> {

        let sorcerer = self.get_mut(id)?;
        update_fn(sorcerer)?;
        sorcerer.updated_at = chrono::Utc::now().to_rfc3339();

        Ok(&*sorcerer)
    }

    fn delete(&mut self, id: &str) -> Result<(), ServiceError> {
        let sorcerer = self.get_mut(id)?;
        sorcerer.active = false;
        sorcerer.updated_at = chrono::Utc::now().to_rfc3339();
        Ok(())
    }
}

// 外部サービス(モック)
struct ExternalGradeService;

impl ExternalGradeService {
    fn validate_grade_eligibility(&self, power: i32, grade: &str) -> Result<bool, ServiceError> {
        // 外部サービス呼び出しをシミュレート
        std::thread::sleep(Duration::from_millis(100));

        // 意図的にエラーを発生させる場合
        if power == 9999 {
            return Err(ServiceError::ExternalServiceError("等級サービスが利用できません".to_string()));
        }

        let min_power = match grade {
            "特級" => 2000,
            "1級" => 1000,
            "2級" => 500,
            "3級" => 200,
            "4級" => 0,
            _ => return Ok(false),
        };

        Ok(power >= min_power)
    }
}

// メインサービス
struct SorcererService {
    store: SorcererStore,
    config: ServiceConfig,
    logger: Logger,
    rate_limiter: RateLimiter,
    retry_policy: RetryPolicy,
    grade_service: ExternalGradeService,
}

impl SorcererService {
    fn new(config: ServiceConfig) -> Self {
        let logger = Logger::new(config.log_level.clone());
        let rate_limiter = RateLimiter::new(config.rate_limit_per_minute);
        let retry_policy = RetryPolicy::new(config.max_retry_attempts, config.retry_delay_ms);

        SorcererService {
            store: SorcererStore::new(),
            config,
            logger,
            rate_limiter,
            retry_policy,
            grade_service: ExternalGradeService,
        }
    }

    fn create_sorcerer(&mut self, request_id: String, client_id: String, name: String, power: i32, grade: String)
        -> ApiResponse<Sorcerer> {

        let mut context = HashMap::new();
        context.insert("request_id".to_string(), request_id.clone());
        context.insert("client_id".to_string(), client_id.clone());
        context.insert("name".to_string(), name.clone());

        // レート制限チェック
        if let Err(error) = self.rate_limiter.check_rate_limit(&client_id) {
            self.logger.warn("Rate limit exceeded", &context);
            return ApiResponse::error(error, request_id);
        }

        // 外部サービスでの等級検証(リトライ付き)
        let grade_validation = self.retry_policy.execute(|| {
            self.grade_service.validate_grade_eligibility(power, &grade)
        });

        match grade_validation {
            Ok(is_eligible) => {
                if !is_eligible {
                    let error = ServiceError::ValidationError(
                        format!("呪力{}では{}等級の資格がありません", power, grade)
                    );
                    self.logger.warn("Grade validation failed", &context);
                    return ApiResponse::error(error, request_id);
                }
            },
            Err(error) => {
                self.logger.error("External grade validation failed", &error, &context);
                return ApiResponse::error(error, request_id);
            }
        }

        // 呪術師作成
        match self.store.create(name, power, grade) {
            Ok(sorcerer) => {
                self.logger.info("Sorcerer created successfully", &context);
                ApiResponse::success(sorcerer, request_id)
            },
            Err(error) => {
                self.logger.error("Failed to create sorcerer", &error, &context);
                ApiResponse::error(error, request_id)
            }
        }
    }

    fn get_sorcerer(&mut self, request_id: String, client_id: String, id: String)
        -> ApiResponse<Sorcerer> {

        let mut context = HashMap::new();
        context.insert("request_id".to_string(), request_id.clone());
        context.insert("client_id".to_string(), client_id.clone());
        context.insert("sorcerer_id".to_string(), id.clone());

        if let Err(error) = self.rate_limiter.check_rate_limit(&client_id) {
            return ApiResponse::error(error, request_id);
        }

        match self.store.get(&id) {
            Ok(sorcerer) => {
                self.logger.info("Sorcerer retrieved", &context);
                ApiResponse::success(sorcerer.clone(), request_id)
            },
            Err(error) => {
                self.logger.warn("Sorcerer not found", &context);
                ApiResponse::error(error, request_id)
            }
        }
    }

    fn add_technique(&mut self, request_id: String, client_id: String, id: String, technique: String)
        -> ApiResponse<Sorcerer> {

        let mut context = HashMap::new();
        context.insert("request_id".to_string(), request_id.clone());
        context.insert("sorcerer_id".to_string(), id.clone());
        context.insert("technique".to_string(), technique.clone());

        if let Err(error) = self.rate_limiter.check_rate_limit(&client_id) {
            return ApiResponse::error(error, request_id);
        }

        let result = self.store.update(&id, |sorcerer| {
            sorcerer.add_technique(technique)
        });

        match result {
            Ok(sorcerer) => {
                self.logger.info("Technique added successfully", &context);
                ApiResponse::success(sorcerer.clone(), request_id)
            },
            Err(error) => {
                self.logger.error("Failed to add technique", &error, &context);
                ApiResponse::error(error, request_id)
            }
        }
    }

    fn list_sorcerers(&mut self, request_id: String, client_id: String)
        -> ApiResponse<Vec<Sorcerer>> {

        if let Err(error) = self.rate_limiter.check_rate_limit(&client_id) {
            return ApiResponse::error(error, request_id);
        }

        let sorcerers: Vec<Sorcerer> = self.store.list().into_iter().cloned().collect();

        let mut context = HashMap::new();
        context.insert("request_id".to_string(), request_id.clone());
        context.insert("count".to_string(), sorcerers.len().to_string());

        self.logger.info("Sorcerers listed", &context);
        ApiResponse::success(sorcerers, request_id)
    }
}

fn main() {
    println!("=== 呪術師管理マイクロサービス ===");

    let config = ServiceConfig::default();
    let mut service = SorcererService::new(config);

    // テストクライアント
    let client_id = "test_client".to_string();
    let mut request_counter = 1;

    // 呪術師作成テスト
    println!("\\n1. 呪術師作成テスト");

    let test_cases = vec![
        ("五条悟", 3000, "特級"),
        ("虎杖悠仁", 1200, "1級"),
        ("無効テスト", 100, "特級"), // 等級不適格
        ("エラーテスト", 9999, "特級"), // 外部サービスエラー
    ];

    let mut created_ids = Vec::new();

    for (name, power, grade) in test_cases {
        let request_id = format!("req_{}", request_counter);
        request_counter += 1;

        let response = service.create_sorcerer(
            request_id,
            client_id.clone(),
            name.to_string(),
            power,
            grade.to_string()
        );

        if response.success {
            if let Some(sorcerer) = response.data {
                println!("✅ 作成成功: {} (ID: {})", sorcerer.name, sorcerer.id);
                created_ids.push(sorcerer.id);
            }
        } else {
            println!("❌ 作成失敗: {}", response.error.unwrap_or("Unknown error".to_string()));
        }
    }

    // 呪術師取得テスト
    println!("\\n2. 呪術師取得テスト");

    for id in &created_ids {
        let request_id = format!("req_{}", request_counter);
        request_counter += 1;

        let response = service.get_sorcerer(request_id, client_id.clone(), id.clone());

        if response.success {
            if let Some(sorcerer) = response.data {
                println!("✅ 取得成功: {} (呪力: {})", sorcerer.name, sorcerer.power);
            }
        } else {
            println!("❌ 取得失敗: {}", response.error.unwrap_or("Unknown error".to_string()));
        }
    }

    // 術式追加テスト
    println!("\\n3. 術式追加テスト");

    if !created_ids.is_empty() {
        let techniques = vec!["無下限呪術", "術式順転『蒼』", "黒閃"];

        for (i, technique) in techniques.iter().enumerate() {
            if i < created_ids.len() {
                let request_id = format!("req_{}", request_counter);
                request_counter += 1;

                let response = service.add_technique(
                    request_id,
                    client_id.clone(),
                    created_ids[i].clone(),
                    technique.to_string()
                );

                if response.success {
                    println!("✅ 術式追加成功: {}", technique);
                } else {
                    println!("❌ 術式追加失敗: {}", response.error.unwrap_or("Unknown error".to_string()));
                }
            }
        }
    }

    // 一覧取得テスト
    println!("\\n4. 一覧取得テスト");

    let request_id = format!("req_{}", request_counter);
    let response = service.list_sorcerers(request_id, client_id.clone());

    if response.success {
        if let Some(sorcerers) = response.data {
            println!("✅ 一覧取得成功: {}件", sorcerers.len());
            for sorcerer in sorcerers {
                println!("  - {} (ID: {}, 呪力: {}, 術式数: {})",
                         sorcerer.name, sorcerer.id, sorcerer.power, sorcerer.techniques.len());
            }
        }
    } else {
        println!("❌ 一覧取得失敗: {}", response.error.unwrap_or("Unknown error".to_string()));
    }

    // レート制限テスト
    println!("\\n5. レート制限テスト");

    for i in 1..=5 {
        let request_id = format!("rate_test_{}", i);
        let response = service.list_sorcerers(request_id, "rate_test_client".to_string());

        if response.success {
            println!("✅ リクエスト{}: 成功", i);
        } else {
            println!("❌ リクエスト{}: {}", i, response.error.unwrap_or("Unknown error".to_string()));
        }
    }

    println!("\\n=== マイクロサービステスト完了 ===");
}

// 注意: このコードを実行するには Cargo.toml に以下を追加してください:
// [dependencies]
// chrono = { version = "0.4", features = ["serde"] }

章末総括

第3章の練習問題、素晴らしい戦いだった!反転術式編の全ての技術を実践で確認できたな。

これらの問題を通して学んだこと:

  • Option型 - 安全な不在の表現
  • Result型 - 型安全なエラーハンドリング
  • カスタムエラー - ドメイン固有の表現力
  • エラーの合成 - 複雑なシステムでの統合
  • 実用的パターン - 実際のプロジェクトでの応用

五条先生の最終アドバイス

エラーハンドリングは最初は面倒に感じるかもしれない。でも一度マスターすれば、プログラムがクラッシュすることはほぼなくなる。

俺の反転術式と同じで、一見ネガティブな「エラー」という概念を、型安全で表現力豊かな力に変換できるようになった。

次は第4章「領域展開編」でジェネリクスとトレイトを学ぼう。より抽象的で強力な概念の習得だ。


「エラーハンドリングを極めれば、失敗すら成功の一部となる」