Rust 100 Ex 🏃【15/37】 Result型 ～Rust流エラーハンドリング術～

前の記事

- [【0】 準備](https://qiita.com/namn1125/items/bd1b4cd028874189a3c1) ← 初回
- ...
- [【14】 フィールド付き列挙型とOption型 ～チョクワガタ～](https://qiita.com/namn1125/items/6fe69d1cab6c7936c698) ← 前回
- 【15】 Result型 ～Rust流エラーハンドリング術～ ← 今回

<details><summary>全記事一覧</summary>

- [【0】 準備](https://qiita.com/namn1125/items/bd1b4cd028874189a3c1)
- [【1】 構文・整数・変数](https://qiita.com/namn1125/items/c776768baec89b8a0438)
- [【2】 if・パニック・演習](https://qiita.com/namn1125/items/13870965e8273bc87bce)
- [【3】 可変・ループ・オーバーフロー](https://qiita.com/namn1125/items/4f57f8aede2f5201016f)
- [【4】 キャスト・構造体 (たまにUFCS)](https://qiita.com/namn1125/items/227ed5bbedbb775c5d27)
- [【5】 バリデーション・モジュールの公開範囲 ～ → カプセル化！～](https://qiita.com/namn1125/items/46721cceccbfa9ec3c7a)
- [【6】 カプセル化の続きと所有権とセッター ～そして不変参照と可変参照！～](https://qiita.com/namn1125/items/442de7798d49f777384c)
- [【7】 スタック・ヒープと参照のサイズ ～メモリの話～](https://qiita.com/namn1125/items/780d80e4340f0e34dbf6)
- [【8】 デストラクタ(変数の終わり)・トレイト ～終わりと始まり～](https://qiita.com/namn1125/items/7247edde3b6bd0b85b25)
- [【9】 Orphan rule (孤児ルール)・演算子オーバーロード・derive ～Empowerment 💪 ～](https://qiita.com/namn1125/items/e565713b423301a80c76)
- [【10】 トレイト境界・文字列・Derefトレイト ～トレイトのアレコレ～](https://qiita.com/namn1125/items/9060faee2965f23e0e3d)
- [【11】 Sized トレイト・From トレイト・関連型 ～おもしろトレイトと関連型～](https://qiita.com/namn1125/items/b68990712d379a3d6a49)
- [【12】 Clone・Copy・Dropトレイト ～覚えるべき主要トレイトたち～](https://qiita.com/namn1125/items/c6f34945f331c7229746)
- [【13】 トレイトまとめ・列挙型・match式 ～最強のトレイトの次は、最強の列挙型～](https://qiita.com/namn1125/items/6b2cc31d6a47f9ffaff6)
- [【14】 フィールド付き列挙型とOption型 ～チョクワガタ～](https://qiita.com/namn1125/items/6fe69d1cab6c7936c698)
- [【15】 Result型 ～Rust流エラーハンドリング術～](https://qiita.com/namn1125/items/698aafaf22ec4bfd1bde)
- [【16】 Errorトレイトと外部クレート ～依存はCargo.tomlに全部お任せ！～](https://qiita.com/namn1125/items/9dae7b728fa0668e4102)
- [【17】 thiserror・TryFrom ～トレイトもResultも自由自在！～](https://qiita.com/namn1125/items/d67006a29b7ff5d6148c)
- [【18】 Errorのネスト・慣例的な書き方 ～Rustらしさの目醒め～](https://qiita.com/namn1125/items/705829027661a38e4a4f)
- [【19】 配列・動的配列 ～スタックが使われる配列と、ヒープに保存できる動的配列～](https://qiita.com/namn1125/items/9c49fd4c08f3e61896c7)
- [【20】 動的配列のリサイズ・イテレータ ～またまたトレイト登場！～](https://qiita.com/namn1125/items/6e4dede789c6de97f396)
- [【21】 イテレータ・ライフタイム ～ライフタイム注釈ようやく登場！～](https://qiita.com/namn1125/items/9a1224abf733f29a980a)
- [【22】 コンビネータ・RPIT ～ :dragon: 「 `Iterator` トレイトを実装してるやつ」～](https://qiita.com/namn1125/items/27fe20f2d60803c94357)
- [【23】 `impl Trait` ・スライス ～配列の欠片～](https://qiita.com/namn1125/items/7601d0268293ce7fcf28)
- [【24】 可変スライス・下書き構造体 ～構造体で状態表現～](https://qiita.com/namn1125/items/a9acc7dc2ca7c4c55cc3)
- [【25】 インデックス・可変インデックス ～インデックスもトレイト！～](https://qiita.com/namn1125/items/bd43375362af10ea8b42)
- [【26】 HashMap・順序・BTreeMap ～Rustの辞書型～](https://qiita.com/namn1125/items/a2806cded12fa719a1cb)
- [【27】 スレッド・'staticライフタイム ～並列処理に見るRustの恩恵～](https://qiita.com/namn1125/items/5a4a09172834063268b1)
- [【28】 リーク・スコープ付きスレッド ～ライフタイムに技あり！～](https://qiita.com/namn1125/items/34a0aacd4d40f9234aef)
- [【29】 チャネル・参照の内部可変性 ～Rustの虎の子、mpscと `Rc<RefCell<T>>`～](https://qiita.com/namn1125/items/861e72dbb4b837508948)
- [【30】 双方向通信・リファクタリング ～返信用封筒を入れよう！～](https://qiita.com/namn1125/items/46f03de967d0de46737e)
- [【31】 上限付きチャネル・PATCH機能 ～パンクしないように制御！～](https://qiita.com/namn1125/items/d7ae8a205ee969339401)
- [【32】 `Send`・排他的ロック(`Mutex`)・非対称排他的ロック(`RwLock`) ～真打 `Arc<Mutex<T>>` 登場～](https://qiita.com/namn1125/items/eee890dc8672e975e70c)
- [【33】 チャネルなしで実装・Syncの話 ～考察回です～](https://qiita.com/namn1125/items/cc8bee3871b727e564e3)
- [【34】 `async fn`・非同期タスク生成 ～Rustの非同期入門～](https://qiita.com/namn1125/items/9c32b730a283ad76c891)
- [【35】 非同期ランタイム・Futureトレイト ～非同期のお作法～](https://qiita.com/namn1125/items/c840d48f8f558613ec10)
- [【36】 ブロッキング・非同期用の実装・キャンセル ～ラストスパート！～](https://qiita.com/namn1125/items/39c139da3f1f26264353)
- [【37】 Axumでクラサバ！ ～最終回～](https://qiita.com/namn1125/items/bae492f823fbe60e9974)
- [【おまけ1】 Rustで勘違いしていたこと3選 🏄🌴 【100 Exercises To Learn Rust 🦀 完走記事 🏃】](https://qiita.com/namn1125/items/2a59f486cce30fd2b889)
- [【おまけ2】 【🙇 懺悔 🙇】Qiitanグッズ欲しさに1日に33記事投稿した話またはQiita CLIとcargo scriptを布教する的な何か](https://qiita.com/namn1125/items/8e552694b389c5b13b5e)
</details>

[100 Exercise To Learn Rust](https://rust-exercises.com/) 演習第15回になります！

今回の関連ページ

- [5.6. Fallibility](https://rust-exercises.com/05_ticket_v2/06_fallibility)
- [5.7. Unwrap](https://rust-exercises.com/05_ticket_v2/07_unwrap)
- [5.8. Error enums](https://rust-exercises.com/05_ticket_v2/08_error_enums)

# [[05_ticket_v2/06_fallibility](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/05_ticket_v2/06_fallibility/src/lib.rs)] Rust用エラーハンドリング機構 Result型

- [5.6. Fallibility](https://rust-exercises.com/05_ticket_v2/06_fallibility)

問題はこちらです。

```rust:lib.rs
// TODO: Convert the `Ticket::new` method to return a `Result` instead of panicking.
//   Use `String` as the error type.

#[derive(Debug, PartialEq)]
struct Ticket {
    title: String,
    description: String,
    status: Status,
}

#[derive(Debug, PartialEq)]
enum Status {
    ToDo,
    InProgress { assigned_to: String },
    Done,
}

impl Ticket {
    pub fn new(title: String, description: String, status: Status) -> Ticket {
        if title.is_empty() {
            panic!("Title cannot be empty");
        }
        if title.len() > 50 {
            panic!("Title cannot be longer than 50 bytes");
        }
        if description.is_empty() {
            panic!("Description cannot be empty");
        }
        if description.len() > 500 {
            panic!("Description cannot be longer than 500 bytes");
        }

        Ticket {
            title,
            description,
            status,
        }
    }
}
```

<details><summary>テストを含めた全体</summary>

```rust:lib.rs
// TODO: Convert the `Ticket::new` method to return a `Result` instead of panicking.
//   Use `String` as the error type.

#[derive(Debug, PartialEq)]
struct Ticket {
    title: String,
    description: String,
    status: Status,
}

#[derive(Debug, PartialEq)]
enum Status {
    ToDo,
    InProgress { assigned_to: String },
    Done,
}

impl Ticket {
    pub fn new(title: String, description: String, status: Status) -> Ticket {
        if title.is_empty() {
            panic!("Title cannot be empty");
        }
        if title.len() > 50 {
            panic!("Title cannot be longer than 50 bytes");
        }
        if description.is_empty() {
            panic!("Description cannot be empty");
        }
        if description.len() > 500 {
            panic!("Description cannot be longer than 500 bytes");
        }

        Ticket {
            title,
            description,
            status,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use common::{overly_long_description, overly_long_title, valid_description, valid_title};

    #[test]
    fn title_cannot_be_empty() {
        let error = Ticket::new("".into(), valid_description(), Status::ToDo).unwrap_err();
        assert_eq!(error, "Title cannot be empty");
    }

    #[test]
    fn description_cannot_be_empty() {
        let error = Ticket::new(valid_title(), "".into(), Status::ToDo).unwrap_err();
        assert_eq!(error, "Description cannot be empty");
    }

    #[test]
    fn title_cannot_be_longer_than_fifty_chars() {
        let error =
            Ticket::new(overly_long_title(), valid_description(), Status::ToDo).unwrap_err();
        assert_eq!(error, "Title cannot be longer than 50 bytes");
    }

    #[test]
    fn description_cannot_be_longer_than_500_chars() {
        let error =
            Ticket::new(valid_title(), overly_long_description(), Status::ToDo).unwrap_err();
        assert_eq!(error, "Description cannot be longer than 500 bytes");
    }
}
```
</details>

ついに... [第5回](https://qiita.com/namn1125/items/46721cceccbfa9ec3c7a) の懸念事項が解消されます！

## 解説

```diff_rust:lib.rs
impl Ticket {
-     pub fn new(title: String, description: String, status: Status) -> Ticket {
+     pub fn new(title: String, description: String, status: Status) -> Result<Ticket, String> {
        if title.is_empty() {
-             panic!("Title cannot be empty");
+             return Err("Title cannot be empty".to_string());
        }

        // 以降も同様の改変

        if title.len() > 50 {
            return Err("Title cannot be longer than 50 bytes".to_string());
        }
        if description.is_empty() {
            return Err("Description cannot be empty".to_string());
        }
        if description.len() > 500 {
            return Err("Description cannot be longer than 500 bytes".to_string());
        }

        Ok(Ticket {
            title,
            description,
            status,
        })
    }
}
```

パニックを使わずに `Result` 型を使うことによって **エラーを値として扱えるようになります** 。この `Result` 型は、「Rustは難しいから扱いたくないけど、Result型だけは〇〇にも実装されてくれないかな...」と他言語でももっぱら人気だったり有名だったりする機能です！

`Result` 型もそれ自体の定義はとてもシンプルです！

```rust:Rust
enum Result<T, E> {
    Ok(T),
    Err(E),
}
```

別にオレオレ `Result` 型もいくらでも使用して良く、筆者も面倒な時はよく `E` を `String` にして運用したりします、というか演習問題もそうなっています。( `E` にはトレイト境界をつけることが多いですがそれはまた別な機会とかに...)

<!-- // 書いたけど無駄になった...
```rust:Rust
use rand::prelude::*;

fn hoge<R: Rng>(rng: &mut R) -> Result<String, String> {
    vec![
        Ok("おｋです！".to_string()),
        Err("エラーです...".to_string()),
    ].as_slice().choose(rng).unwrap().clone()
}

fn main() {
    let mut rng = thread_rng();
    
    let s = match hoge(&mut rng) {
        Ok(v) => v,
        Err(e) => e,
    };
    
    println!("{}", s);
}
```
-->

何が言いたいかというと、 `Option` 型と同じで慣れてしまうと何も怖くなく分かりやすいということです。複雑な例外のためのフローや構文を覚える必要はありません！

`Option` 型のところで言い忘れましたが、 `Option` 型も `Result` 型も、 **関数のシグネチャに入ってくる** という良さがあります。「この関数は `None` を返してくるかもしれない」、「この関数は `Result` を返してくるかもしれない」ということが、シグネチャを見るだけで分かるようになります。(一昔前の)他言語だと `NULL` や例外に怯えドキュメントやソースコードを良く読まなければなりませんが、Rustでは型システムがどうなっているかを読むだけで、つまり眼の前のソースコードとIDE(VSCode)の補完だけで解決できてしまうことがしばしばあります。

# [[05_ticket_v2/07_unwrap](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/05_ticket_v2/07_unwrap/src/lib.rs)] とりあえず `.unwrap()`

- [5.7. Unwrap](https://rust-exercises.com/05_ticket_v2/07_unwrap)

問題はこちらです。

```rust:lib.rs
// TODO: `easy_ticket` should panic when the title is invalid.
//   When the description is invalid, instead, it should use a default description:
//   "Description not provided".
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
    todo!()
}

#[derive(Debug, PartialEq, Clone)]
struct Ticket {
    title: String,
    description: String,
    status: Status,
}

// 以降テスト以外は前回の回答と同じ
```

<details><summary>全体</summary>

```rust:lib.rs
// TODO: `easy_ticket` should panic when the title is invalid.
//   When the description is invalid, instead, it should use a default description:
//   "Description not provided".
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
    todo!()
}

#[derive(Debug, PartialEq, Clone)]
struct Ticket {
    title: String,
    description: String,
    status: Status,
}

#[derive(Debug, PartialEq, Clone)]
enum Status {
    ToDo,
    InProgress { assigned_to: String },
    Done,
}

impl Ticket {
    pub fn new(title: String, description: String, status: Status) -> Result<Ticket, String> {
        if title.is_empty() {
            return Err("Title cannot be empty".to_string());
        }
        if title.len() > 50 {
            return Err("Title cannot be longer than 50 bytes".to_string());
        }
        if description.is_empty() {
            return Err("Description cannot be empty".to_string());
        }
        if description.len() > 500 {
            return Err("Description cannot be longer than 500 bytes".to_string());
        }

        Ok(Ticket {
            title,
            description,
            status,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use common::{overly_long_description, overly_long_title, valid_description, valid_title};

    #[test]
    #[should_panic(expected = "Title cannot be empty")]
    fn title_cannot_be_empty() {
        easy_ticket("".into(), valid_description(), Status::ToDo);
    }

    #[test]
    fn template_description_is_used_if_empty() {
        let ticket = easy_ticket(valid_title(), "".into(), Status::ToDo);
        assert_eq!(ticket.description, "Description not provided");
    }

    #[test]
    #[should_panic(expected = "Title cannot be longer than 50 bytes")]
    fn title_cannot_be_longer_than_fifty_chars() {
        easy_ticket(overly_long_title(), valid_description(), Status::ToDo);
    }

    #[test]
    fn template_description_is_used_if_too_long() {
        let ticket = easy_ticket(valid_title(), overly_long_description(), Status::ToDo);
        assert_eq!(ticket.description, "Description not provided");
    }
}
```
</details>

Description周りのエラーの時はデフォルトとなる値をセットして、それ以外の時はパニックするようにしてほしいという問題です。

## 解説

`Err(E)` の時はパニックにしてしまう `unwrap` を利用します。一番上の `main` 関数などでは `unwrap` を使ってパニックしてしまうのが楽ですね。なんだかんだ `Result` 型で一番お世話になるメソッドです。

```rust:lib.rs
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
    let ticket_w = Ticket::new(title.clone(), description, status.clone());

    match ticket_w {
        Ok(ticket) => ticket,
        Err(s) if s.starts_with("Description") => {
            Ticket::new(title, "Description not provided".to_string(), status).unwrap()
        }
        // t => t.unwrap(), // 網羅性は良いけど可読性的には良くない
        Err(s) => panic!("{}", s), // 網羅性は見づらいが可読性は良い
    }
}
```

この回まで使っていなかった機能として、 `match` 式のパターンマッチの枝に `if` 式のようなものを付けていますが、これは[マッチガード](https://doc.rust-jp.rs/book-ja/ch18-03-pattern-syntax.html#%E3%83%9E%E3%83%83%E3%83%81%E3%82%AC%E3%83%BC%E3%83%89%E3%81%A7%E8%BF%BD%E5%8A%A0%E3%81%AE%E6%9D%A1%E4%BB%B6%E5%BC%8F)と呼ばれるものです。 `match` 式で書きたいのだけどパターンとしては隠れてしまう条件を書くのにちょうどよい機能になっています。

マッチガードを使い、 `Description` で始まっているエラーの時だけデフォルトのDescriptionになっているチケットを返すようにしています。

パニックに関してですが、いくつか書き方があります。コメントに書いた通りなのでお好みのものを選べば良さそうです。筆者は `Err(s)` 派かなぁ...場合にもよりそうです。

# [[05_ticket_v2/08_error_enums](https://github.com/mainmatter/100-exercises-to-learn-rust/blob/main/exercises/05_ticket_v2/08_error_enums/src/lib.rs)] エラーも列挙体で管理しよう

- [5.8. Error enums](https://rust-exercises.com/05_ticket_v2/08_error_enums)

問題はこちらです。

```rust:lib.rs
// TODO: Use two variants, one for a title error and one for a description error.
//   Each variant should contain a string with the explanation of what went wrong exactly.
//   You'll have to update the implementation of `Ticket::new` as well.
enum TicketNewError {}

// TODO: `easy_ticket` should panic when the title is invalid, using the error message
//   stored inside the relevant variant of the `TicketNewError` enum.
//   When the description is invalid, instead, it should use a default description:
//   "Description not provided".
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
    todo!()
}

#[derive(Debug, PartialEq)]
struct Ticket {
    title: String,
    description: String,
    status: Status,
}

// 省略
```

<details><summary>テストを含めた全体</summary>

```rust:lib.rs
// TODO: Use two variants, one for a title error and one for a description error.
//   Each variant should contain a string with the explanation of what went wrong exactly.
//   You'll have to update the implementation of `Ticket::new` as well.
enum TicketNewError {}

// TODO: `easy_ticket` should panic when the title is invalid, using the error message
//   stored inside the relevant variant of the `TicketNewError` enum.
//   When the description is invalid, instead, it should use a default description:
//   "Description not provided".
fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
    todo!()
}

#[derive(Debug, PartialEq)]
struct Ticket {
    title: String,
    description: String,
    status: Status,
}

#[derive(Debug, PartialEq, Clone)]
enum Status {
    ToDo,
    InProgress { assigned_to: String },
    Done,
}

impl Ticket {
    pub fn new(
        title: String,
        description: String,
        status: Status,
    ) -> Result<Ticket, TicketNewError> {
        if title.is_empty() {
            return Err("Title cannot be empty".to_string());
        }
        if title.len() > 50 {
            return Err("Title cannot be longer than 50 bytes".to_string());
        }
        if description.is_empty() {
            return Err("Description cannot be empty".to_string());
        }
        if description.len() > 500 {
            return Err("Description cannot be longer than 500 bytes".to_string());
        }

        Ok(Ticket {
            title,
            description,
            status,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use common::{overly_long_description, overly_long_title, valid_description, valid_title};

    #[test]
    #[should_panic(expected = "Title cannot be empty")]
    fn title_cannot_be_empty() {
        easy_ticket("".into(), valid_description(), Status::ToDo);
    }

    #[test]
    fn template_description_is_used_if_empty() {
        let ticket = easy_ticket(valid_title(), "".into(), Status::ToDo);
        assert_eq!(ticket.description, "Description not provided");
    }

    #[test]
    #[should_panic(expected = "Title cannot be longer than 50 bytes")]
    fn title_cannot_be_longer_than_fifty_chars() {
        easy_ticket(overly_long_title(), valid_description(), Status::ToDo);
    }

    #[test]
    fn template_description_is_used_if_too_long() {
        let ticket = easy_ticket(valid_title(), overly_long_description(), Status::ToDo);
        assert_eq!(ticket.description, "Description not provided");
    }
}
```
</details>

先程の問題でマッチガードを利用してスタイリッシュに `match` 式で分岐を書きましたが、「複数の値を取りうる...？そもそも列挙体で良いのでは...？」ということでそのリファクタをし、エラーの内容も列挙体で表そうという問題です！

## 解説

エラーは2種類なので、2種類のバリアントを持つ列挙体 `TicketNewError` を定義します。 `match` 式の枝の方は、パターンマッチはネストして表現できることを利用し、 `DescriptionError` の時はデフォルトのDescriptionを返すようにしています。

```rust:lib.rs (前半)
#[derive(Debug)]
enum TicketNewError {
    TitleError(String),
    DescriptionError(String),
}

fn easy_ticket(title: String, description: String, status: Status) -> Ticket {
    let ticket_w = Ticket::new(title.clone(), description, status.clone());

    use TicketNewError::*;

    match ticket_w {
        Ok(ticket) => ticket,
        Err(TitleError(s)) => panic!("{}", s),
        Err(DescriptionError(_)) => {
            Ticket::new(title, "Description not provided".to_string(), status).unwrap()
        }
    }
}
```

後半の方も列挙体を使う方法に直す必要がありますね。

```diff_rust:lib.rs (後半)
impl Ticket {
    pub fn new(
        title: String,
        description: String,
        status: Status,
    ) -> Result<Ticket, TicketNewError> {
        if title.is_empty() {
-             return Err("Title cannot be empty".to_string());
+             return Err(TicketNewError::TitleError(
+                 "Title cannot be empty".to_string(),
+             ));
        }

        // 以降のバリデーションにも同様の改変
        // ...省略...

        Ok(Ticket {
            title,
            description,
            status,
        })
    }
}
```

`Result` 型には慣れてきたでしょうか...？ここまで紹介してきた列挙型というシンプルなシステムを使っているだけなのに、大分複雑な見た目になってきました...逆に言うと、複雑に見えるRustの機能は案外こんな感じでシンプルということです。 `Result` 周りはもう少し複雑な感じになっていきますが、100 Exercisesは丁寧にフォローアップしていってくれるようです。

では次の問題に行きましょう！

次の記事: [【16】 Errorトレイトと外部クレート ～依存はCargo.tomlに全部お任せ！～](https://qiita.com/namn1125/items/9dae7b728fa0668e4102)