【Effective-Rustlings-jp】Day 4：標準のErrorを使おう

# はじめに
こんにちは。細々とプログラミングをしている[sotanengel](https://x.com/sota_eng_prog)です。
この記事は以下の記事の連載です。

https://qiita.com/sotanengel/private/78103dcfe2fa34f8815e


<details>
<summary><strong>他の連載記事</strong> (<em>詳細</em>)</summary>

- [Day 1：型システムを使ってデータ構造を再現しよう](https://qiita.com/sotanengel/private/588a7f7cc03c36fc1adc)
- [Day 2：型システムを用いて共通の挙動を表現しよう](https://qiita.com/sotanengel/private/2a8fc62b6d9f8e916021)
- [Day 3：OptionとResultに対してはmatchを用いずに変換しよう](https://qiita.com/sotanengel/items/b54990dc82d877b9d70d)
- [Day 4：標準のErrorを使おう](https://qiita.com/sotanengel/private/79f683c3d75d09244567)
- [Day 5：型変換を理解しよう](https://qiita.com/sotanengel/private/8cc28c8b6108e5998e23)
- [Day 6：newtypeパターンを活用しよう](https://qiita.com/sotanengel/private/38814f8b5a88aad9eccc)
- [Day 7：複雑な型にはビルダを使おう](https://qiita.com/sotanengel/private/80bc555e95ed56b72f14)
- [Day 8：明示的なループの代わりにイテレータ変換を使用することを検討しよう](https://qiita.com/sotanengel/private/f7f4fc03f0b2dc445bf2)
- [Day 9：標準トレイトに習熟しよう](https://qiita.com/sotanengel/private/24a8ad443fc23e779ccd)
- [Day 10：RIIパターンにはDropトレイトを実装しよう](https://qiita.com/sotanengel/private/a3d1b04f98ea80b8989d)
- [Day 11：ジェネリクスとトレイトオブジェクトのトレードオフを理解しよう](https://qiita.com/sotanengel/private/8e2e88d5619b434c3ada)
- [Day 12：デフォルト実装を用いて、実装しなければならないトレイトメソッドを最小限にしよう](https://qiita.com/sotanengel/private/16b02d32732727eaf66f)
- [Day 13：Don't panic](https://qiita.com/sotanengel/private/aa833ce99c8e8b8eecf2)
- [Day 14：リフレクションを避けよう](https://qiita.com/sotanengel/private/6108b22f6644d4fde997)
- [Day 15：可視範囲を最小化しよう](https://qiita.com/sotanengel/private/4075925e05de50dfac96)
- [Day 16：ワイルドカードインポートを避けよう](https://qiita.com/sotanengel/private/555314f77c8318bb1c45)
- [Day 17：パブリックインターフェースのドキュメントを書こう](https://qiita.com/sotanengel/private/b4e8a11522b253ebb62e)
- [Day 18：分別を持ってマクロを使おう](https://qiita.com/sotanengel/private/a33b5ab66a82a26b4adc)
- [Day 19：Clippyに耳を傾けよう](https://qiita.com/sotanengel/private/b4c161bb87fc9e7e813e)
- [Day 20：ユニットテスト以上のものを書こう](https://qiita.com/sotanengel/private/58fcafaee5418b2b2b16)

</details>

また本記事は[Effective Rust(David Drysdale (著), 中田 秀基 (翻訳))](https://www.oreilly.com/library/view/effective-rust/9781098151393/)を参考に作成されております。とてもいい書籍ですので興味を持った方は、ぜひ読んでみてください！
<div style="text-align: center;">
  <figure>
    <a href="https://www.amazon.co.jp/Effective-Rust-%E2%80%95Rust%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E6%94%B9%E5%96%84%E3%81%97%E3%80%81%E3%82%A8%E3%82%B3%E3%82%B7%E3%82%B9%E3%83%86%E3%83%A0%E3%82%92%E6%9C%80%E5%A4%A7%E9%99%90%E3%81%AB%E6%B4%BB%E7%94%A8%E3%81%99%E3%82%8B%E3%81%9F%E3%82%81%E3%81%AE35%E9%A0%85%E7%9B%AE-David-Drysdale/dp/4814400942"  target="_blank">
      <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1758960/cb1245be-02d6-0c74-8b79-e46288f2ea73.png" alt="商品画像" width="50%">
    </a>
  </figure>
</div>

# 今日の内容
## 概要
Rustの`Error`トレイトを独自のエラー型に実装することで、呼び出し側に適切にエラー情報を伝えられるようにしよう。

## String情報を持つ独自のエラーを実装しよう
### 問([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/exercises/4_types_Prefer_idiomatic_error_types/1_my_error_string.rs))
`String`を`Error`としてそのまま使うことはできないため、独自の型(`OriginalError`)を定義しましょう。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
// TODO: StringはErrorを実装していないため、formatが使えるように独自の型を定義し、Errorを実装してください。
fn do_something(flag: bool) -> Result<(), String> {
    if flag {
        Ok(())
    } else {
        "Something went wrong".to_string()
    }
}

fn main() {
    match do_something(false) {
        Ok(_) => println!("Success!"),
        Err(e) => {
            let error_message = format!("Failed: {}", e);
            println!("{}", error_message);
        }
    }
}
```

</details>

### 解答([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/solutions/4_types_Prefer_idiomatic_error_types/1_my_error_string.rs))
`From`トレイトも実装することで、`String`からの変換も容易にすることができます。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
use std::error;
use std::fmt;

// 独自エラー型の定義 (タプル構造体)
#[derive(Debug)]
pub struct OriginalError(String);

// Displayトレイトを実装してエラーメッセージを整える
impl fmt::Display for OriginalError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error: {}", self.0)
    }
}

// error::Errorトレイトを実装
impl error::Error for OriginalError {}

// From<String> を実装して String からの変換を簡単に
impl From<String> for OriginalError {
    fn from(message: String) -> Self {
        OriginalError(message)
    }
}

// &str からの変換もできるようにする
impl From<&str> for OriginalError {
    fn from(message: &str) -> Self {
        OriginalError(message.to_string())
    }
}

fn do_something(flag: bool) -> Result<(), OriginalError> {
    if flag {
        Ok(())
    } else {
        Err(OriginalError::from("Something went wrong"))
    }
}

fn main() {
    match do_something(false) {
        Ok(_) => println!("Success!"),
        Err(e) => {
            let error_message = format!("Failed: {}", e);
            println!("{}", error_message);
        }
    }
}
```

</details>

## エラーをネストしよう
### 問([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/exercises/4_types_Prefer_idiomatic_error_types/2_nest_error.rs))
`error::Error`トレイトで`source`メソッドを`AppError`に実装することで`?`でエラーを伝播できるようにしましょう。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
use std::error;
use std::fmt;
use std::io;
use std::string::FromUtf8Error;

#[derive(Debug)]
pub enum AppError {
    NotFound(io::Error),
    InvalidInput(FromUtf8Error),
    InternalError(Box<dyn error::Error>),
}

// Displayトレイトを実装してエラーメッセージを整える
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::NotFound(e) => write!(f, "Not Found: {}", e),
            AppError::InvalidInput(e) => write!(f, "Invalid Input: {}", e),
            AppError::InternalError(e) => write!(f, "Internal Error: {}", e),
        }
    }
}

// TODO: sourceメソッドを記載して、processの処理で?でエラーを伝播できるようにしてください。
impl error::Error for AppError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {}
}

// From<String> を実装して String からの変換を簡単に
impl From<io::Error> for AppError {
    fn from(err: io::Error) -> Self {
        AppError::NotFound(err)
    }
}

impl From<FromUtf8Error> for AppError {
    fn from(err: FromUtf8Error) -> Self {
        AppError::InvalidInput(err)
    }
}

impl From<String> for AppError {
    fn from(message: String) -> Self {
        AppError::InternalError(Box::new(io::Error::new(io::ErrorKind::Other, message)))
    }
}

// サンプル関数
fn do_something(input: &str) -> Result<(), AppError> {
    if input.is_empty() {
        Err(io::Error::new(
            io::ErrorKind::NotFound,
            "Input cannot be empty",
        ))?
    } else if input == "404" {
        Err(AppError::from("Requested resource not found".to_string()))?
    } else if input == "500" {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Unexpected error occurred",
        ))?
    }
    Ok(())
}

// プロセス関数をResultでラップ
fn process() -> Result<(), AppError> {
    let inputs = vec!["", "404", "success"];

    for input in inputs {
        do_something(input)?; // エラーがあれば即座にreturn
        println!("Processed: {}", input);
    }

    Ok(())
}

fn main() {}

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

    #[test]
    fn test_do_something_not_found() {
        let result = do_something("");
        match result {
            Err(AppError::NotFound(e)) => {
                assert_eq!(e.kind(), io::ErrorKind::NotFound);
                assert_eq!(e.to_string(), "Input cannot be empty");
            }
            _ => panic!("Expected NotFound error"),
        }
    }

    #[test]
    fn test_do_something_internal_error() {
        let result = do_something("404");
        match result {
            Err(AppError::InternalError(e)) => {
                assert!(e.to_string().contains("Requested resource not found"));
            }
            _ => panic!("Expected InternalError"),
        }
    }

    #[test]
    fn test_do_something_success() {
        let result = do_something("success");
        assert!(result.is_ok());
    }

    #[test]
    fn test_process() {
        let result = process();
        match result {
            Err(AppError::NotFound(e)) => {
                assert_eq!(e.to_string(), "Input cannot be empty");
            }
            Err(AppError::InternalError(e)) => {
                assert!(e.to_string().contains("Requested resource not found"));
            }
            Ok(_) => {} // 成功ケースは特にアサートしない
            _ => panic!("Unexpected error"),
        }
    }

    #[test]
    fn test_from_io_error() {
        let io_err = io::Error::new(io::ErrorKind::Other, "io error");
        let app_error: AppError = io_err.into();
        match app_error {
            AppError::NotFound(e) => {
                assert_eq!(e.to_string(), "io error");
            }
            _ => panic!("Expected NotFound error"),
        }
    }

    #[test]
    fn test_from_utf8_error() {
        let utf8_err = String::from_utf8(vec![0, 159]).unwrap_err();
        let app_error: AppError = AppError::from(utf8_err);
        match app_error {
            AppError::InvalidInput(e) => {
                assert!(e.to_string().contains("invalid utf-8"));
            }
            _ => panic!("Expected InvalidInput error"),
        }
    }

    #[test]
    fn test_from_string() {
        let app_error: AppError = String::from("custom error").into();
        match app_error {
            AppError::InternalError(e) => {
                assert_eq!(e.to_string(), "custom error");
            }
            _ => panic!("Expected InternalError"),
        }
    }
}
```

</details>

### 解答([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/solutions/4_types_Prefer_idiomatic_error_types/2_nest_error.rs))
コード参照。

<details>
<summary><strong>コード</strong> (<em>詳細</em>)</summary>

```rust
use std::error;
use std::fmt;
use std::io;
use std::string::FromUtf8Error;

#[derive(Debug)]
pub enum AppError {
    NotFound(io::Error),
    InvalidInput(FromUtf8Error),
    InternalError(Box<dyn error::Error>),
}

// Displayトレイトを実装してエラーメッセージを整える
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::NotFound(e) => write!(f, "Not Found: {}", e),
            AppError::InvalidInput(e) => write!(f, "Invalid Input: {}", e),
            AppError::InternalError(e) => write!(f, "Internal Error: {}", e),
        }
    }
}

// Errorトレイトを実装
impl error::Error for AppError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            AppError::NotFound(e) => Some(e),
            AppError::InvalidInput(e) => Some(e),
            AppError::InternalError(e) => Some(e.as_ref()),
        }
    }
}

// From<String> を実装して String からの変換を簡単に
impl From<io::Error> for AppError {
    fn from(err: io::Error) -> Self {
        AppError::NotFound(err)
    }
}

impl From<FromUtf8Error> for AppError {
    fn from(err: FromUtf8Error) -> Self {
        AppError::InvalidInput(err)
    }
}

impl From<String> for AppError {
    fn from(message: String) -> Self {
        AppError::InternalError(Box::new(io::Error::new(io::ErrorKind::Other, message)))
    }
}

// サンプル関数
fn do_something(input: &str) -> Result<(), AppError> {
    if input.is_empty() {
        Err(io::Error::new(
            io::ErrorKind::NotFound,
            "Input cannot be empty",
        ))?
    } else if input == "404" {
        Err(AppError::from("Requested resource not found".to_string()))?
    } else if input == "500" {
        Err(io::Error::new(
            io::ErrorKind::Other,
            "Unexpected error occurred",
        ))?
    }
    Ok(())
}

// プロセス関数をResultでラップ
fn process() -> Result<(), AppError> {
    let inputs = vec!["", "404", "success"];

    for input in inputs {
        do_something(input)?; // エラーがあれば即座にreturn
        println!("Processed: {}", input);
    }

    Ok(())
}

fn main() {}

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

    #[test]
    fn test_do_something_not_found() {
        let result = do_something("");
        match result {
            Err(AppError::NotFound(e)) => {
                assert_eq!(e.kind(), io::ErrorKind::NotFound);
                assert_eq!(e.to_string(), "Input cannot be empty");
            }
            _ => panic!("Expected NotFound error"),
        }
    }

    #[test]
    fn test_do_something_internal_error() {
        let result = do_something("404");
        match result {
            Err(AppError::InternalError(e)) => {
                assert!(e.to_string().contains("Requested resource not found"));
            }
            _ => panic!("Expected InternalError"),
        }
    }

    #[test]
    fn test_do_something_success() {
        let result = do_something("success");
        assert!(result.is_ok());
    }

    #[test]
    fn test_process() {
        let result = process();
        match result {
            Err(AppError::NotFound(e)) => {
                assert_eq!(e.to_string(), "Input cannot be empty");
            }
            Err(AppError::InternalError(e)) => {
                assert!(e.to_string().contains("Requested resource not found"));
            }
            Ok(_) => {} // 成功ケースは特にアサートしない
            _ => panic!("Unexpected error"),
        }
    }

    #[test]
    fn test_from_io_error() {
        let io_err = io::Error::new(io::ErrorKind::Other, "io error");
        let app_error: AppError = io_err.into();
        match app_error {
            AppError::NotFound(e) => {
                assert_eq!(e.to_string(), "io error");
            }
            _ => panic!("Expected NotFound error"),
        }
    }

    #[test]
    fn test_from_utf8_error() {
        let utf8_err = String::from_utf8(vec![0, 159]).unwrap_err();
        let app_error: AppError = AppError::from(utf8_err);
        match app_error {
            AppError::InvalidInput(e) => {
                assert!(e.to_string().contains("invalid utf-8"));
            }
            _ => panic!("Expected InvalidInput error"),
        }
    }

    #[test]
    fn test_from_string() {
        let app_error: AppError = String::from("custom error").into();
        match app_error {
            AppError::InternalError(e) => {
                assert_eq!(e.to_string(), "custom error");
            }
            _ => panic!("Expected InternalError"),
        }
    }
}
```

</details>

# さいごに

もしも本リポジトリで不備などあれば、リポジトリのissueやPRなどでご指摘いただければと思います。

https://github.com/sotanengel/effective-rustlings-jp/issues