エラーハンドリング Result<T, E>

Rustのエラーハンドリングは以下の2種類にグループ分けされる

  • 回復不可能なエラー panic!
  • 回復可能なエラー Result<T, E>

Result<T, E>

Rustには例外処理が存在しないため、その代わりにResult<T, E>を使う。

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

各variant

  • Ok(T) 成功時
  • Err(E) 失敗時

match 式によるハンドリング

例1: ファイルを開く

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("faild to open file: {:?}", error),
    };
}

Note:

match 式を使って、成功したときは値を取得し、失敗時はpanic!マクロでプログラムを終了させている。

更に取り出したerrorを解析する場合

match式を取り替える

エラーハンドリングがどんどんネストしていくとmatch式もネストしていく。

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");
    let f = match f {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("failed to create the file: {:?}", e),
            },
            _ => panic!("Problem opening the file: {:?}", error),
        }
    }

}

Result<T, E> に実装された unwrap_or_else メソッドに置き換えることができる。

use std::fs::File;
use std::io::ErrorKind;
fn main () {
    let f = File::open("hello.txt").unwrap_or_else ( |error| {
        if error.kind() == Error::NotFound {
            File::create("hello.txt").unwrap_or_else( |error| {
                panic!("Problem creating the file: {:?}", error);
            });
        } else {
            panic!("Problem opening the file: {:?}", error);
        }
    });
}

panic!のためのショートカット

以下の2つのメソッドを使うことでmatch式を使うことなく、エラー発生時、panic!を発生させることができる

  • unwrap() メッセージなし
  • expect(message) panic!マクロにメッセージを渡す
use std::fs::File;
fn main() {
    let f = File::open("hello.txt").unwrap();
}

以下と同等

use std::fs::File;
fn main() {
    let f = File::open("hello.txt");
    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("{}", error),
    }
}

expect メソッドは unwrap と似ているが、 エラーメッセージを渡すことができるので、unwrap よりも expect を使う。

use std::fs::File;
fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

エラーの伝搬

関数内部でエラーを処理するのではなく、呼び出し元に Result<T, E> を返すことで、呼び出し側でエラーのハンドリングが行える。

例: ファイルの中身をStringに読み込む

use std::fs::File;
use std::io;
use std::io::Read;

fn read_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e), // Result<T, io::Error>を返す
    }

    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

エラーの伝搬のためのショートカット

? 演算子を使うことで、エラーの伝搬をショートカットすることができる

std::fs::File;
std::io;
std::io::Read;

fn read_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt")?;
    let s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s);
}

? 演算子Result の後ろに置くことで、match 式と同様に、エラー発生時、Err型を自動で返してくれる。

Errの生成は、内部でFromトレイトを通して生成される。

また ? 演算子を使うことでボイラープレートを削除し、シンプルにする。

use std::fs::File;
use std::io;
use std::io::Read;

fn read_from_file() -> Result<String, io::Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}

参照

https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html

https://doc.rust-lang.org/std/result/enum.Result.html

https://rust-lang-nursery.github.io/rust-cookbook/errors/handle.html