RustのResult型：エラー処理を簡単に

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/ca42ec8e-9729-421e-aed4-199ed062cc87.png)](https://leapcell.io/?lc_t=q_rustresult)

## Rust における Result 型

Rust はシステムプログラミング言語であり、独自のエラー処理メカニズムを提供しています。Rust では、エラーは 2 つのカテゴリに分けられます：回復可能なエラーと回復不可能なエラーです。回復可能なエラーに対して、Rust は `Result` 型を提供しています。

## Result 型の定義

`Result` 型は列挙型であり、`Ok` と `Err` の 2 つのバリアントを持ちます。`Ok` バリアントは操作の成功を示し、成功した値を含みます。一方、`Err` バリアントは操作の失敗を示し、エラーの値を含みます。

以下は `Result` 型の定義です：

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

ここで、`T` は成功時の値の型、`E` はエラー時の値の型を表します。

## Result 型の用途

`Result` 型は通常、関数の戻り値として使用されます。関数が正常に実行された場合、`Ok` バリアントを返し、失敗した場合は `Err` バリアントを返します。

以下は簡単な例です：

```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(numerator / denominator)
    }
}

fn main() {
    let result = divide(4.0, 2.0);
    match result {
        Ok(value) => println!("Result: {}", value),
        Err(e) => println!("Error: {}", e),
    }
}
```

この例では、`divide` 関数が 2 つの引数（分子と分母）を受け取ります。もし分母が `0` の場合、`Err` バリアントを返し、それ以外は `Ok` バリアントを返します。

`main` 関数では `divide` 関数を呼び出し、`match` 式を使って戻り値を処理します。`Ok` バリアントが返ってきた場合は結果を表示し、`Err` バリアントが返ってきた場合はエラーメッセージを表示します。

## Result 型を使用したエラー処理方法

`Result` 型を返す関数を呼び出す際、発生し得るエラーを適切に処理する必要があります。Rust では、`Result` 型のエラーを処理するためにいくつかの方法が提供されています。

### `match` 式を使う

`match` 式は Rust において `Result` 型のエラーを処理する最も一般的な方法です。戻り値の異なる状況に応じて、異なる処理を実行できます。

```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(numerator / denominator)
    }
}

fn main() {
    let result = divide(4.0, 2.0);
    match result {
        Ok(value) => println!("Result: {}", value),
        Err(e) => println!("Error: {}", e),
    }
}
```

この例では、`match` 式を使って `divide` 関数の戻り値を処理し、`Ok` の場合は結果を表示し、`Err` の場合はエラーを表示します。

### `if let` を使う

`if let` 式は `match` の簡略版であり、特定の 1 つのバリアントにだけ関心がある場合に便利です。

```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(numerator / denominator)
    }
}

fn main() {
    let result = divide(4.0, 2.0);
    if let Ok(value) = result {
        println!("Result: {}", value);
    }
}
```

この例では、`Ok` バリアントが返ってきた場合のみ結果を表示し、`Err` バリアントが返ってきた場合は何もしません。

### `?` 演算子を使う

`?` 演算子を使うと、関数内で発生したエラーを簡単に外部に伝播できます。

```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(numerator / denominator)
    }
}

fn calculate(numerator: f64, denominator: f64) -> Result<f64, String> {
    let result = divide(numerator, denominator)?;
    Ok(result * 2.0)
}

fn main() {
    let result = calculate(4.0, 2.0);
    match result {
        Ok(value) => println!("Result: {}", value),
        Err(e) => println!("Error: {}", e),
    }
}
```

この例では、`calculate` 関数内で `divide` 関数を呼び出し、`?` を使ってエラーを簡単に処理しています。`divide` が `Err` を返した場合、`calculate` も即座に `Err` を返します。

## Result 型の便利なメソッド

`Result` 型には、エラー処理をより簡単にするためのいくつかの便利なメソッドが用意されています。

### `is_ok` と `is_err` メソッド

`is_ok` メソッドは `Result` が `Ok` であるかを確認し、`is_err` メソッドは `Err` であるかを確認します。

```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(numerator / denominator)
    }
}

fn main() {
    let result = divide(4.0, 2.0);
    if result.is_ok() {
        println!("Result: {}", result.unwrap());
    } else {
        println!("Error: {}", result.unwrap_err());
    }
}
```

この例では、`is_ok` を使って `Ok` かどうかを判定し、`Ok` なら `unwrap` で値を取得し、`Err` なら `unwrap_err` でエラーを取得します。

### `unwrap` と `unwrap_err` メソッド

`unwrap` メソッドは `Ok` の値を取得し、もし `Err` だった場合は `panic` を発生させます。同様に、`unwrap_err` は `Err` の値を取得し、もし `Ok` だった場合は `panic` します。

```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(numerator / denominator)
    }
}

fn main() {
    let result = divide(4.0, 2.0);
    let value = result.unwrap(); // エラーが発生すると panic
    println!("Result: {}", value);
}
```

この例では、`unwrap` を使って `Ok` の値を取得しようとしていますが、もし `Err` が返ってきた場合、`panic` します。

### `expect` と `expect_err` メソッド

`expect` や `expect_err` メソッドは `unwrap` の代わりに、エラーメッセージを指定できるメソッドです。

```rust
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
    if denominator == 0.0 {
        Err("Cannot divide by zero".to_string())
    } else {
        Ok(numerator / denominator)
    }
}

fn main() {
    let result = divide(4.0, 2.0);
    let value = result.expect("Failed to divide"); // カスタムエラーメッセージ
    println!("Result: {}", value);
}
```

この例では、もし `Err` だった場合、`"Failed to divide"` というカスタムメッセージとともに `panic` します。

## Result 型の特性と利点

`Result` 型には、以下のような特徴と利点があります：

- **エラーの明示的な処理**：`Result` 型を使うことで、エラー処理を明示的に記述する必要があり、エラーを見落とすことが減ります。
- **型安全**：`Result` はジェネリック型であり、成功値とエラー値の型を明確に指定できます。これにより、型の安全性が保証され、誤った型変換を防げます。
- **エラー伝播の容易さ**：`?` 演算子を使うことで、エラーを簡単に呼び出し元へ伝播できます。
- **エラーの組み合わせが容易**：`Result` 型には `and`、`or`、`and_then`、`or_else` などの組み合わせメソッドがあり、複数の `Result` 型の値を簡単に操作できます。

## 実際のコードにおける Result の使用

実際のプログラムでは、独自のエラー型を定義し、それを `Result` 型と組み合わせて使用することが一般的です。

以下に、カスタムエラー型を定義して `Result` を使用する例を示します：

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

type Result<T> = std::result::Result<T, MyError>;

#[derive(Debug)]
enum MyError {
    DivideByZero,
    ParseIntError(ParseIntError),
}

impl From<ParseIntError> for MyError {
    fn from(e: ParseIntError) -> Self {
        MyError::ParseIntError(e)
    }
}

fn divide(numerator: &str, denominator: &str) -> Result<f64> {
    let numerator: f64 = numerator.parse()?;
    let denominator: f64 = denominator.parse()?;
    if denominator == 0.0 {
        Err(MyError::DivideByZero)
    } else {
        Ok(numerator / denominator)
    }
}

fn main() {
    let result = divide("4", "2");
    match result {
        Ok(value) => println!("Result: {}", value),
        Err(e) => println!("Error: {:?}", e),
    }
}
```

### このコードのポイント：

- `MyError` というカスタムエラー型を定義し、`ParseIntError` も含められるようにしています。
- `From<T>` トレイトを実装することで、`ParseIntError` を `MyError` に変換できるようにしています。
- `divide` 関数は `&str` 型の引数を受け取り、それを `f64` に変換し、ゼロ除算を検出して適切な `Err` を返します。

## Result を使ったファイル入出力エラーの処理

ファイルの読み書きを行う際、ファイルが存在しない、アクセス権がないなどのエラーが発生することがあります。これらのエラーは `Result` 型を用いて処理できます。

以下に、ファイルを読み込む関数を `Result` を使って実装した例を示します：

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

fn read_file(path: &str) -> Result<String, io::Error> {
    fs::read_to_string(path)
}

fn main() {
    let result = read_file("test.txt");
    match result {
        Ok(content) => println!("File content: {}", content),
        Err(e) => println!("Error: {}", e),
    }
}
```

### コードのポイント：

- `fs::read_to_string` は `Result<String, io::Error>` を返します。
- `read_file` 関数は `Result<String, io::Error>` を返すようにし、エラーが発生した場合はそのまま呼び出し元へ返します。
- `main` 関数では `match` を使ってエラーを処理し、成功時はファイルの内容を表示し、失敗時はエラーメッセージを表示します。

## Result を使ったネットワークエラーの処理

ネットワーク通信を行う際、接続タイムアウトやサーバーエラーなど、さまざまなエラーが発生する可能性があります。これらのエラーも `Result` を使って処理できます。

以下に、`TcpStream::connect` を用いてサーバーへ接続し、エラーを処理する例を示します：

```rust
use std::io;
use std::net::TcpStream;

fn connect(host: &str) -> Result<TcpStream, io::Error> {
    TcpStream::connect(host)
}

fn main() {
    let result = connect("example.com:80");
    match result {
        Ok(stream) => println!("Connected to {}", stream.peer_addr().unwrap()),
        Err(e) => println!("Error: {}", e),
    }
}
```

### コードのポイント：

- `TcpStream::connect` は `Result<TcpStream, io::Error>` を返します。
- `connect` 関数は `Result<TcpStream, io::Error>` をそのまま返し、エラーが発生した場合は呼び出し元に伝えます。
- `main` 関数では `match` を使って接続成功時に `peer_addr` を表示し、失敗時はエラーメッセージを表示します。

## Result とエラー処理のベストプラクティス

Rust において `Result` を適切に使うためには、いくつかのベストプラクティスを守るとよいでしょう：

1. **カスタムエラー型を定義する**  
   `MyError` のようなカスタムエラー型を作成することで、エラーの種類を整理しやすくなります。

2. **`?` 演算子でエラーを伝播する**  
   `?` を使うことで、エラー処理を簡潔に記述できます。

3. **`unwrap` や `expect` の使用を控える**  
   これらのメソッドは `Err` の場合に `panic` を引き起こすため、適切に `match` や `if let` を使ってエラーを処理することが推奨されます。

4. **複数の `Result` を組み合わせる**  
   `and_then`、`or_else` などのメソッドを使うと、複数の `Result` 型の値を組み合わせた処理がスムーズになります。

## まとめ

Rust の `Result` 型は、安全で堅牢なエラー処理を実現するための強力なツールです。  
この型を適切に活用することで、エラーを明示的に処理し、コードの可読性と保守性を向上させることができます。

---

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

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/d0b4a573-8364-4d57-b2a6-2898fecc5fb7.png)](https://leapcell.io/?lc_t=q_rustresult)

[Leapcell](https://leapcell.io/?lc_t=q_rustresult)は、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/29cee1ec-b316-40bb-b4ef-8da8ed7910b1.png)](https://leapcell.io/?lc_t=q_rustresult)

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

---

[ブログでこの記事を読む](https://leapcell.io/blog/mastering-rust-result-enum)