Rustのエラー処理を極める：ResultとOptionを超えて

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/ebbcfd55-02aa-4939-aa60-5c21297ef667.png)](https://leapcell.io/?lc_t=q_rusterr)

## エラー処理に関するメソッド

エラーをスムーズに処理するには、公式が提供するメソッドを適切に活用することが重要です。うまく活用すれば効率が大幅に向上します。

- `or()`
- `and()`
- `or_else()`
- `and_then()`
- `map()`
- `map_err()`
- `map_or()`
- `map_or_else()`
- `ok_or()`
- `ok_or_else()`
- …

以下では、これらのメソッドをどのような場面で使うべきか、どのように使うのかを詳しく説明し、最終的に `Err` 型を設計する際のポイントについても紹介します。

### `or()`, `and()`

`or()` と `and()` は、どちらか一方を選択する際に使用します。論理演算の OR（または）と AND（かつ）に似ています。

- `or()` は、式を順番に評価し、いずれかの式の結果が `Some` または `Ok` であれば、その値を即座に返します。
- `and()` は、両方の式の結果が `Some` または `Ok` の場合に、2 つ目の式の値を返します。どちらか一方が `None` または `Err` の場合は、その値を即座に返します。

```rust
let s1 = Some("some1");
let s2 = Some("some2");
let n: Option<&str> = None;

let o1: Result<&str, &str> = Ok("ok1");
let o2: Result<&str, &str> = Ok("ok2");
let e1: Result<&str, &str> = Err("error1");
let e2: Result<&str, &str> = Err("error2");

assert_eq!(s1.or(s2), s1); // Some1 or Some2 = Some1
assert_eq!(s1.or(n), s1); // Some or None = Some
assert_eq!(n.or(s1), s1); // None or Some = Some
assert_eq!(n.or(n), n); // None1 or None2 = None2

assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 = Ok1
assert_eq!(o1.or(e1), o1); // Ok or Err = Ok
assert_eq!(e1.or(o1), o1); // Err or Ok = Ok
assert_eq!(e1.or(e2), e2); // Err1 or Err2 = Err2

assert_eq!(s1.and(s2), s2); // Some1 and Some2 = Some2
assert_eq!(s1.and(n), n); // Some and None = None
assert_eq!(n.and(s1), n); // None and Some = None
assert_eq!(n.and(n), n); // None1 and None2 = None1

assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 = Ok2
assert_eq!(o1.and(e1), e1); // Ok and Err = Err
assert_eq!(e1.and(o1), e1); // Err and Ok = Err
assert_eq!(e1.and(e2), e1); // Err1 and Err2 = Err1
```

### `or_else()`, `and_then()`

`or()` と `and()` は単純な選択をするだけで、値を変更することはできません。もしエラー時に異なる処理を実行したい場合は、クロージャ（無名関数）を使う必要があります。そのために `or_else()` と `and_then()` を使用します。

```rust
// `or_else()` の Option 版
let s1 = Some("some1");
let s2 = Some("some2");
let fn_some = || Some("some2");

let n: Option<&str> = None;
let fn_none = || None;

assert_eq!(s1.or_else(fn_some), s1); // Some1 or_else Some2 = Some1
assert_eq!(s1.or_else(fn_none), s1); // Some or_else None = Some
assert_eq!(n.or_else(fn_some), s2); // None or_else Some = Some
assert_eq!(n.or_else(fn_none), None); // None1 or_else None2 = None2

// `or_else()` の Result 版
let o1: Result<&str, &str> = Ok("ok1");
let o2: Result<&str, &str> = Ok("ok2");
let fn_ok = |_| Ok("ok2");

let e1: Result<&str, &str> = Err("error1");
let e2: Result<&str, &str> = Err("error2");
let fn_err = |_| Err("error2");

assert_eq!(o1.or_else(fn_ok), o1); // Ok1 or_else Ok2 = Ok1
assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err = Ok
assert_eq!(e1.or_else(fn_ok), o2); // Err or_else Ok = Ok
assert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 = Err2
```

### `map()`

`Result` や `Option` の値を変換する場合は `map()` を使用します。

```rust
let s1 = Some("abcde");
let s2 = Some(5);

let n1: Option<&str> = None;
let n2: Option<usize> = None;

let o1: Result<&str, &str> = Ok("abcde");
let o2: Result<usize, &str> = Ok(5);

let e1: Result<&str, &str> = Err("abcde");
let e2: Result<usize, &str> = Err("abcde");

let fn_character_count = |s: &str| s.chars().count();

assert_eq!(s1.map(fn_character_count), s2); // Some1 map = Some2
assert_eq!(n1.map(fn_character_count), n2); // None1 map = None2

assert_eq!(o1.map(fn_character_count), o2); // Ok1 map = Ok2
assert_eq!(e1.map(fn_character_count), e2); // Err1 map = Err2
```

### `map_err()`

`Result` の `Err` の値を変更する場合は `map_err()` を使用します。

```rust
let o1: Result<&str, &str> = Ok("abcde");
let o2: Result<&str, isize> = Ok("abcde");

let e1: Result<&str, &str> = Err("404");
let e2: Result<&str, isize> = Err(404);

let fn_character_count = |s: &str| -> isize { s.parse().unwrap() };

assert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map = Ok2
assert_eq!(e1.map_err(fn_character_count), e2); // Err1 map = Err2
```

### `map_or()`

エラーが発生しない場合、デフォルト値を返す `map_or()` を使用できます。

```rust
const V_DEFAULT: u32 = 1;

let s: Result<u32, ()> = Ok(10);
let n: Option<u32> = None;
let fn_closure = |v: u32| v + 2;

assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12);
assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT);
```

### `map_or_else()`

`map_or()` はデフォルト値を固定で指定しますが、`map_or_else()` を使えば、クロージャを使ってデフォルト値を動的に生成できます。

```rust
let s = Some(10);
let n: Option<i8> = None;

let fn_closure = |v: i8| v + 2;
let fn_default = || 1;

assert_eq!(s.map_or_else(fn_default, fn_closure), 12);
assert_eq!(n.map_or_else(fn_default, fn_closure), 1);

let o = Ok(10);
let e = Err(5);
let fn_default_for_result = |v: i8| v + 1;

assert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);
assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6);
```

### `ok_or()`

`Option` を `Result` に変換する場合は `ok_or()` を使用します。

```rust
const ERR_DEFAULT: &str = "error message";

let s = Some("abcde");
let n: Option<&str> = None;

let o: Result<&str, &str> = Ok("abcde");
let e: Result<&str, &str> = Err(ERR_DEFAULT);

assert_eq!(s.ok_or(ERR_DEFAULT), o); // Some(T) -> Ok(T)
assert_eq!(n.ok_or(ERR_DEFAULT), e); // None -> Err(default)
```

### `ok_or_else()`

`ok_or()` は固定のエラーメッセージを返しますが、`ok_or_else()` を使えばクロージャでエラーメッセージを動的に作成できます。

```rust
let s = Some("abcde");
let n: Option<&str> = None;
let fn_err_message = || "error message";

let o: Result<&str, &str> = Ok("abcde");
let e: Result<&str, &str> = Err("error message");

assert_eq!(s.ok_or_else(fn_err_message), o); // Some(T) -> Ok(T)
assert_eq!(n.ok_or_else(fn_err_message), e); // None -> Err(default)
```

## `Error` の設計

初心者は `Result` の型の違いに苦しむことが多いですが、`Result` の型を明確に理解すれば、エラー処理で困ることはなくなります。

### シンプルなカスタムエラー

通常、プログラムでは独自のエラー型を定義します。まずは最もシンプルな `Result` のカスタムエラーを作成しましょう。

```rust
use std::fmt;

#[derive(Debug)]
struct CustomError;

impl fmt::Display for CustomError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "An Error Occurred, Please Try Again!")
    }
}

fn make_error() -> Result<(), CustomError> {
    Err(CustomError)
}

fn main() {
    match make_error() {
        Err(e) => eprintln!("{}", e),
        _ => println!("No error"),
    }

    eprintln!("{:?}", make_error());
}
```

`eprintln!` は `println!` と基本的に同じですが、標準エラー出力に出力されます。

カスタムエラーに `Debug` と `Display` を実装することで、エラーを適切に表示できます。また、`Box<dyn std::error::Error>` に変換するためにも必要になります。

### より複雑なカスタムエラー

現実のプロジェクトでは、エラーコードやエラーメッセージを含めることが多いです。

```rust
use std::fmt;

struct CustomError {
    code: usize,
    message: String,
}

impl fmt::Display for CustomError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let err_msg = match self.code {
            404 => "Sorry, Can not find the Page!",
            _ => "Sorry, something is wrong! Please Try Again!",
        };

        write!(f, "{}", err_msg)
    }
}

impl fmt::Debug for CustomError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "CustomError {{ code: {}, message: {} }}",
            self.code, self.message
        )
    }
}

fn make_error() -> Result<(), CustomError> {
    Err(CustomError {
        code: 404,
        message: String::from("Page not found"),
    })
}

fn main() {
    match make_error() {
        Err(e) => eprintln!("{}", e), // "Sorry, Can not find the Page!"
        _ => println!("No error"),
    }

    eprintln!("{:?}", make_error()); // "CustomError { code: 404, message: Page not found }"
}
```

このように `Display` と `Debug` を手動実装することで、エラーの表現を自由にカスタマイズできます。

### エラーの変換

通常、プログラムではさまざまなライブラリの `Result` を扱いますが、それらの `Err` 型は統一されていません。異なるエラー型を統一するために `std::convert::From` を利用します。

```rust
use std::fs::File;
use std::io;

#[derive(Debug)]
struct CustomError {
    kind: String,
    message: String,
}

impl From<io::Error> for CustomError {
    fn from(error: io::Error) -> Self {
        CustomError {
            kind: String::from("io"),
            message: error.to_string(),
        }
    }
}

fn main() -> Result<(), CustomError> {
    let _file = File::open("nonexistent_file.txt")?;

    Ok(())
}
```

このコードでは、`?` を使うことで `std::io::Error` を `CustomError` に自動変換しています。

より多くのエラー型を統一するには、さらに `From` を実装すれば OK です。

```rust
use std::fs::File;
use std::io::{self, Read};
use std::num;

#[derive(Debug)]
struct CustomError {
    kind: String,
    message: String,
}

impl From<io::Error> for CustomError {
    fn from(error: io::Error) -> Self {
        CustomError {
            kind: String::from("io"),
            message: error.to_string(),
        }
    }
}

impl From<num::ParseIntError> for CustomError {
    fn from(error: num::ParseIntError) -> Self {
        CustomError {
            kind: String::from("parse"),
            message: error.to_string(),
        }
    }
}

fn main() -> Result<(), CustomError> {
    let mut file = File::open("hello_world.txt")?;

    let mut content = String::new();
    file.read_to_string(&mut content)?;

    let _number: usize;
    _number = content.parse()?;

    Ok(())
}
```

### `Result` の異なる `Err` 型を統一する方法

プログラム内で異なる `Err` 型が混在する場合、以下の 4 つの方法で統一できます。

1. `Box<dyn Error>` を使用
2. 独自のエラー型を定義
3. `thiserror` を使用
4. `anyhow` を使用

#### 1. `Box<dyn Error>` を使用

`std::error::Error` トレイトを実装していれば、`Box<dyn Error>` で統一できます。

```rust
use std::fs::read_to_string;
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
    let html = render()?;
    println!("{}", html);
    Ok(())
}

fn render() -> Result<String, Box<dyn Error>> {
    let file = std::env::var("MARKDOWN")?;
    let source = read_to_string(file)?;
    Ok(source)
}
```

この方法は簡単ですが、以下のデメリットがあります：

- **動的ディスパッチ**：型情報が失われ、パフォーマンスがわずかに低下する。
- **ヒープ割り当て**：エラーをヒープに格納するため、オーバーヘッドが発生する。
- **型安全性の低下**：具体的なエラー型を扱いにくくなる。

#### 2. 独自のエラー型を定義

エラーを列挙型（`enum`）で管理すれば、型安全性が向上します。

```rust
use std::fs::read_to_string;

fn main() -> Result<(), MyError> {
    let html = render()?;
    println!("{}", html);
    Ok(())
}

fn render() -> Result<String, MyError> {
    let file = std::env::var("MARKDOWN")?;
    let source = read_to_string(file)?;
    Ok(source)
}

#[derive(Debug)]
enum MyError {
    EnvironmentVariableNotFound,
    IOError(std::io::Error),
}

impl From<std::env::VarError> for MyError {
    fn from(_: std::env::VarError) -> Self {
        Self::EnvironmentVariableNotFound
    }
}

impl From<std::io::Error> for MyError {
    fn from(value: std::io::Error) -> Self {
        Self::IOError(value)
    }
}

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

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            MyError::EnvironmentVariableNotFound => write!(f, "Environment variable not found"),
            MyError::IOError(err) => write!(f, "IO Error: {}", err.to_string()),
        }
    }
}
```

この方法は型安全ですが、やや冗長です。

#### 3. `thiserror` を使用

`thiserror` を使うと、エラーの定義が簡潔になります。

```rust
use std::fs::read_to_string;
use thiserror::Error;

fn main() -> Result<(), MyError> {
    let html = render()?;
    println!("{}", html);
    Ok(())
}

fn render() -> Result<String, MyError> {
    let file = std::env::var("MARKDOWN")?;
    let source = read_to_string(file)?;
    Ok(source)
}

#[derive(Error, Debug)]
enum MyError {
    #[error("Environment variable not found")]
    EnvironmentVariableNotFound(#[from] std::env::VarError),
    #[error(transparent)]
    IOError(#[from] std::io::Error),
}
```

マクロを使って `From` 実装を自動化できるので、非常に便利です。

#### 4. `anyhow` を使用

`anyhow` は `Box<dyn Error>` のラッパーですが、エラーハンドリングをより簡単にできます。

```rust
use std::fs::read_to_string;
use anyhow::Result;

fn main() -> Result<()> {
    let html = render()?;
    println!("{}", html);
    Ok(())
}

fn render() -> Result<String> {
    let file = std::env::var("MARKDOWN")?;
    let source = read_to_string(file)?;
    Ok(source)
}
```

## Just do it

これで Rust のエラー処理が怖くなくなったはずです。上記の方法を活用すれば、実際の開発でエラー処理に困ることはなくなります！

---

### [私たちはLeapcell、Rustプロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_rusterr)

[![Leapcell](https://cdn1.leapcell.io/1821426051965c7d99-569f-41ee-be38-d7ed034c8a58.png)](https://leapcell.io/?lc_t=q_rusterr)

[Leapcell](https://leapcell.io/?lc_t=q_rusterr)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/08a7db02-5d69-4e17-93b6-7cff204ecf3b.png)](https://leapcell.io/?lc_t=q_rusterr)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/rust-error-handling-deep-dive)