【Effective-Rustlings-jp】Day 13：Don't panic

# はじめに
こんにちは。細々とプログラミングをしている[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ではpanicによって回復不能なバグに対応させることができますが、以下のような問題を内包しています。

1. cargoによって挙動が変えられてしまう([The Cargo Book](https://doc.rust-lang.org/cargo/reference/profiles.html#panic))
    1. "unwind" : パニック時にスタックを巻き戻します
    1. "abort" : パニック時にプロセスを終了します
1. データ構造の作成中にpanicが起こると構造の整合性を保証できない
1. FFI境界との相性が良くない([参考記事](https://qiita.com/tatsuya6502/items/ad2300cdccc232e660b4#ffi-%E3%81%A8-panic))

これらの問題を引き起こさないために、どのような場合にpanicを使うべきか考えましょう。

## どれをpanicにすべきだろうか？
### 問([リンク](https://github.com/sotanengel/effective-rustlings-jp/blob/20250101/exercises/18_concepts_Dont_panic/1_hint_for_panic.rs))
get_critical_envとparse_str_to_intという関数があります。
panic!とResultどちらを使うべきでしょうか？

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

```rust
use std::env;
use std::num::ParseIntError;

// `CRITICAL_ENV` がセットされているか、確認する。
// TODO: このコードはpanic!とResultどちらがいいでしょうか？
fn get_critical_env() -> {
    match env::var("PATH") {
        Ok(value) => value,
        Err(_) => ,
    }
}

// 文字列を整数に変換する関数
// TODO: このコードはpanic!とResultどちらがいいでしょうか？
fn parse_str_to_int(input: &str) ->  {
    input.parse::<i32>()
}

fn main() {
    let critical_value = get_critical_env();
    println!("PATH is set to: {}", critical_value);


    let inputs = vec!["42", "100", "not_number", "256"];
    // TODO: 以下でparse_str_to_intでintに変更して出力してください。
}

// -------------------------------------------------------
// テストコード
// -------------------------------------------------------
#[cfg(test)]
mod tests {
    use super::*;
    use std::env;

    // PATH が正しくセットされている場合のテスト
    //
    // 注意: 環境変数はプロセス全体で共有されるため、テスト並列実行の設定によっては
    // 衝突が起こる可能性があります。実際のプロダクションコードでは
    // テスト時に専用のプロセスを立ち上げるか、シリアル実行するなどの工夫が必要です。
    #[test]
    fn test_get_critical_env_ok() {
        // テスト用に環境変数をセット
        env::set_var("PATH", "test_value");
        let value = get_critical_env();
        assert_eq!(value, "test_value");
    }

    // CRITICAL_ENV がセットされていない場合は panic! することを確認
    #[test]
    #[should_panic(expected = "PATH environment variable is not set! Aborting...")]
    fn test_get_critical_env_panic() {
        // 一旦アンセット
        env::remove_var("PATH");
        // ここで必ず panic! するはず
        let _ = get_critical_env();
    }

    // -------------------------------------------------------
    // parse_str_to_int のテスト (Result の確認)
    // -------------------------------------------------------

    #[test]
    fn parse_str_to_int_success() {
        let result = parse_str_to_int("123");
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 123);
    }

    #[test]
    fn parse_str_to_int_error() {
        let result = parse_str_to_int("not_a_number");
        assert!(result.is_err());
        // エラーの型が ParseIntError であることを確認
        match result {
            Err(e) => {
                // ここではあえてエラーメッセージまでは比較しないが、
                // メッセージを比較する場合は e.to_string() 等を用いる
                assert!(e.to_string().contains("invalid digit"));
            }
            _ => unreachable!(),
        }
    }
}
```

</details>

### 解答([リンク](URL))
コード参照。

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

```rust
use std::env;
use std::num::ParseIntError;

// これは「必須の環境変数」が無いとプログラムを継続できないケースを想定した関数。
// もし `PATH` がセットされていなければ、回復不能なエラーとみなして panic! する。
fn get_critical_env() -> String {
    match env::var("PATH") {
        Ok(value) => value,
        Err(_) => panic!("PATH environment variable is not set! Aborting..."),
    }
}

// -------------------------------------------------------
// Result を使ったエラー処理の例（回復可能なエラー）
// -------------------------------------------------------

// 文字列を整数に変換する関数
// 変換が失敗した場合は ParseIntError を返し、呼び出し元で対処できるようにする
fn parse_str_to_int(input: &str) -> Result<i32, ParseIntError> {
    input.parse::<i32>()
}

fn main() {
    println!("=== めったにないが必須条件が満たされていない場合の panic! ===");

    // 本来であれば CRITICAL_ENV が必ず設定されている前提だが、
    // もし設定が漏れていたらプログラムを継続できないので panic! する。
    let critical_value = get_critical_env();

    println!("PATH is set to: {}", critical_value);

    // 以下は通常の処理(例: サーバー起動など)を続ける
    println!("The program continues with the critical environment value...");

    println!("\n=== Result を使うケース ===");
    let inputs = vec!["42", "100", "not_number", "256"];
    for s in inputs {
        match parse_str_to_int(s) {
            Ok(num) => println!("'{}' -> {}", s, num),
            Err(e) => eprintln!("'{}' は整数に変換できませんでした: {}", s, e),
        }
    }
}

// -------------------------------------------------------
// テストコード
// -------------------------------------------------------
#[cfg(test)]
mod tests {
    use super::*;
    use std::env;

    // PATH が正しくセットされている場合のテスト
    //
    // 注意: 環境変数はプロセス全体で共有されるため、テスト並列実行の設定によっては
    // 衝突が起こる可能性があります。実際のプロダクションコードでは
    // テスト時に専用のプロセスを立ち上げるか、シリアル実行するなどの工夫が必要です。
    #[test]
    fn test_get_critical_env_ok() {
        // テスト用に環境変数をセット
        env::set_var("PATH", "test_value");
        let value = get_critical_env();
        assert_eq!(value, "test_value");
    }

    // PATH がセットされていない場合は panic! することを確認
    #[test]
    #[should_panic(expected = "PATH environment variable is not set! Aborting...")]
    fn test_get_critical_env_panic() {
        // 一旦アンセット
        env::remove_var("PATH");
        // ここで必ず panic! するはず
        let _ = get_critical_env();
    }

    // -------------------------------------------------------
    // parse_str_to_int のテスト (Result の確認)
    // -------------------------------------------------------

    #[test]
    fn parse_str_to_int_success() {
        let result = parse_str_to_int("123");
        assert!(result.is_ok());
        assert_eq!(result.unwrap(), 123);
    }

    #[test]
    fn parse_str_to_int_error() {
        let result = parse_str_to_int("not_a_number");
        assert!(result.is_err());
        // エラーの型が ParseIntError であることを確認
        match result {
            Err(e) => {
                // ここではあえてエラーメッセージまでは比較しないが、
                // メッセージを比較する場合は e.to_string() 等を用いる
                assert!(e.to_string().contains("invalid digit"));
            }
            _ => unreachable!(),
        }
    }
}
```

</details>

# さいごに

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

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