Caution

お使いのブラウザはJavaScriptが実行できない状態になっております。
当サイトはWebプログラミングの情報サイトの為、
JavaScriptが実行できない環境では正しいコンテンツが提供出来ません。
JavaScriptが実行可能な状態でご閲覧頂くようお願い申し上げます。

Rust辞典

  1. トップページ
  2. Rust辞典
  3. カスタムエラー型 / thiserror

カスタムエラー型 / thiserror

Rustでは独自のエラー型を定義して、アプリケーション固有のエラーを型安全に表現できます。enumでエラーの種類を定義し、『Display』トレイトでエラーメッセージを提供するのが基本パターンです。

構文
use std::fmt;
use std::error::Error;

// カスタムエラー型を定義します。
#[derive(Debug)]
enum AppError {
    NotFound(String),
    ParseError(String),
    IoError(std::io::Error),
}

// Display トレイトを実装してエラーメッセージを定義します。
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::NotFound(s) => write!(f, "見つかりません: {}", s),
            AppError::ParseError(s) => write!(f, "パースエラー: {}", s),
            AppError::IoError(e) => write!(f, "I/Oエラー: {}", e),
        }
    }
}

// Error トレイトを実装します(空の実装でOKです)。
impl Error for AppError {}
エラー設計のパターン
パターン概要
enum MyError { ... }エラーの種類をバリアントで定義します。
impl Display for MyErrorユーザー向けのエラーメッセージを定義します(必須)。
impl Error for MyError {}Errorトレイトをマークします(空実装でOK)。
impl From<io::Error> for MyError他のエラー型から変換できるようにします(? 演算子が使えます)。
Box<dyn Error>複数の異なるエラー型を返したい場合のトレイトオブジェクトです。
type Result<T> = std::result::Result<T, MyError>Result型のエイリアスを定義してエラー型を省略します。
サンプルコード
use std::fmt;
use std::error::Error;
use std::num::ParseIntError;

#[derive(Debug)]
enum CalcError {
    DivisionByZero,
    Overflow,
    ParseError(ParseIntError),
}

impl fmt::Display for CalcError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            CalcError::DivisionByZero => write!(f, "ゼロ除算エラーです"),
            CalcError::Overflow => write!(f, "オーバーフローが発生しました"),
            CalcError::ParseError(e) => write!(f, "数値パースエラー: {}", e),
        }
    }
}

impl Error for CalcError {}

// ParseIntError から CalcError に自動変換します(? 演算子を使えます)。
impl From<ParseIntError> for CalcError {
    fn from(e: ParseIntError) -> Self {
        CalcError::ParseError(e)
    }
}

// 自作Result型エイリアスです。
type CalcResult<T> = Result<T, CalcError>;

fn divide(a: i32, b: i32) -> CalcResult<i32> {
    if b == 0 {
        Err(CalcError::DivisionByZero)
    } else {
        Ok(a / b)
    }
}

// ? 演算子でParseIntErrorをCalcErrorに自動変換します。
fn parse_and_divide(a: &str, b: &str) -> CalcResult<i32> {
    let a: i32 = a.parse()?;  // ParseIntError が CalcError::ParseError に変換されます。
    let b: i32 = b.parse()?;
    divide(a, b)
}

fn main() {
    // 正常な計算です。
    match divide(10, 2) {
        Ok(result) => println!("10 / 2 = {}", result),
        Err(e) => println!("エラー: {}", e),
    }

    // ゼロ除算です。
    match divide(10, 0) {
        Ok(result) => println!("結果: {}", result),
        Err(e) => println!("エラー: {}", e),
    }

    // 文字列のパースを含む計算です。
    let cases = vec![
        ("20", "4"),
        ("abc", "4"),
        ("20", "0"),
    ];

    for (a, b) in cases {
        match parse_and_divide(a, b) {
            Ok(result) => println!("{} / {} = {}", a, b, result),
            Err(e) => println!("{} / {} エラー: {} ({:?})", a, b, e, e),
        }
    }

    // Box<dyn Error> を使う簡易版です(複数のエラー型を返せます)。
    fn flexible_error(fail: bool) -> Result<i32, Box<dyn Error>> {
        if fail {
            Err("シンプルなエラー".into())
        } else {
            Ok(42)
        }
    }
    println!("{:?}", flexible_error(false));
    println!("{:?}", flexible_error(true));
}
概要

カスタムエラー型に『From<OtherError>』を実装しておくと、『?』演算子が自動でエラーを変換してくれます。これにより複数の種類のエラーが発生する関数でも、シンプルなコードで書けます。

本格的なアプリケーションでは『thiserror』クレートが便利です。『#[derive(Error)]』マクロで『Display』と『Error』の実装を自動化できます。

Resultの基本操作は『Result::unwrap() / expect() / ? 演算子』を参照してください。

記事の間違いや著作権の侵害等ございましたらお手数ですがまでご連絡頂ければ幸いです。