(DAY20) 俺と学ぶRust言語~エラーハンドリング~

# 今日の内容
- 「Option」と「Result」を組み合わせたエラーハンドリングの応用

# はじめに
[前回](https://qiita.com/FUKAch/items/31f92ba8f9c6e35448aa)は、Resultについて学びました。
今回はOptionとResultを組み合わせたエラーハンドリングの応用について学んでいきます。

# OptionとResultの相互変換
Rustでは、OptionをResultに変換したり、その逆を行うことができます。

### OptionからResultに変換
「値がない(None)」場合にエラーを設定したいときに使います。
```rust
fn get_user_id(input: Option<&str>) -> Result<i32, String> {
    input
        .ok_or_else(|| "値がありません".to_string())
        .and_then(|id_str| id_str.parse::<i32>().map_err(|_| "Please type a number!".to_string()))
}
fn main() {
    let user_id = get_user_id(Some("42"));
    println!("{:?}", user_id);

    let invalid_user_id = get_user_id(Some("abc"));
    println!("{:?}", invalid_user_id);

    let none_user_id = get_user_id(None);
    println!("{:?}", none_user_id);
}
/******** 実行結果 ********
Ok(42)
Err("Please type a number!")
Err("値がありません")
*************************/
```

### ResultからOptionに変換
エラー情報が不要な場合は、ok()を使ってResultをOptionに変換します。
```rust
fn main() {
    let result: Result<i32, &str> = Ok(10);
    let option = result.ok();
    println!("{:?}", option);

    let error_result: Result<i32, &str> = Err("err");
    let option = error_result.ok();
    println!("{:?}", option);
}
/******** 実行結果 ********
Some(10)
None
*************************/
```

# 複数の処理を組み合わせる
以下の例では、ファイルの内容を読み取り、数値を計算する処理を安全に行っています。
```rust
use std::fs::File;
use std::io::{self, Read};

fn read_file_content(path: &str) -> Result<String, io::Error> {
    let mut file = File::open(path)?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    Ok(content)
}

fn calculate_average(data: &str) -> Result<f64, &'static str> {
    let numbers: Result<Vec<f64>, _> = data
        .lines()
        .map(|line| line.trim().parse::<f64>())
        .collect();
    let numbers = numbers.map_err(|_| "parse failed!")?;
    if numbers.is_empty() {
        Err("Data is empty!")
    } else {
        Ok(numbers.iter().sum::<f64>() / numbers.len() as f64)
    }
}

fn main() {
    match read_file_content("data.txt") {
        Ok(content) => match calculate_average(&content) {
            Ok(avg) => println!("average: {:.2}", avg),
            Err(e) => println!("Err: {}", e),
        },
        Err(e) => println!("File error: {}", e),
    }
}
```
- ファイル読み取りのエラーはResultで管理
- 文字列から数値を解析する際もResultでエラーを処理
- matchでネストして、全てのエラーケースに対応

# ?演算子を使った簡略化
ネストを避けるために、?演算子を活用してコードを簡潔に書き直します。
```rust
fn process_file(path: &str) -> Result<f64, Box<dyn std::error::Error>> {
    let content = std::fs::read_to_string(path)?;
    let numbers: Vec<f64> = content
        .lines()
        .map(|line| line.trim().parse::<f64>())
        .collect::<Result<_, _>>()?;
    if numbers.is_empty() {
        return Err("Data is empty".into());
    }
    Ok(numbers.iter().sum::<f64>() / numbers.len() as f64)
}
fn main() {
    match process_file("data.txt") {
        Ok(avg) => println!("avarage: {:.2}", avg),
        Err(e) => println!("Err: {}", e),
    }
}
```

- ?を使ってエラーが発生した場合に、即座に関数を終了できる
- ネストを減らし、コードの可読性を向上

# OptionとResultの併用例
以下のコードでは、入力を処理し、条件に応じてエラーまたは成功を返します。
```rust
fn validate_input(input: Option<&str>) -> Result<i32, &'static str> {
    let input = input.ok_or("No input")?;
    let number: i32 = input.parse().map_err(|_| "please type number!")?;
    if number < 0 {
        Err("Negative values not allowed")
    } else {
        Ok(number)
    }
}
fn main() {
    let inputs = vec![Some("42"), Some("-10"), None, Some("abc")];

    for input in inputs {
        match validate_input(input) {
            Ok(value) => println!("input value: {}", value),
            Err(e) => println!("Err: {}", e),
        }
    }
}
/******** 実行結果 ********
input value: 42
Err: Negative values not allowed
Err: No input
Err: please type number!
*************************/
```

# 複数のエラー型の統合
複数の関数が異なるエラー型を返す場合、統一的に扱うために`Box<dyn std::error::Error>`や`thiserror`クレートを利用します。
```rust
use std::error::Error;

fn read_file(path: &str) -> Result<String, Box<dyn Error>> {
    let content = std::fs::read_to_string(path)?;
    Ok(content)
}

fn parse_content(content: &str) -> Result<Vec<i32>, Box<dyn Error>> {
    let numbers = content
        .lines()
        .map(|line| line.parse::<i32>())
        .collect::<Result<Vec<_>, _>>()?;
    Ok(numbers)
}

fn main() {
    let result = (|| -> Result<(), Box<dyn Error>> {
        let content = read_file("data.txt")?;
        let numbers = parse_content(&content)?;
        println!("Data: {:?}", numbers);
        Ok(())
    })();

    if let Err(e) = result {
        println!("Err: {}", e);
    }
}
```
# おわりに
今回は、OptionとResultを組み合わせたエラーハンドリングの応用について学びました。
次回は、if let について学びたいと思います。
ご精読ありがとうございました。