言語
日本語
English

Caution

お使いのブラウザはJavaScriptが無効になっております。
当サイトでは検索などの処理にJavaScriptを使用しています。
より快適にご利用頂くため、JavaScriptを有効にしたうえで当サイトを閲覧することをお勧めいたします。

Rust辞典

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

カスタムエラー型 / thiserror

対応: Rust 1.0(2015)

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

構文

use std::fmt;
use std::error::Error;

#[derive(Debug)]
enum AppError {
    NotFound(String),
    ParseError(String),
    IoError(std::io::Error),
}

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),
        }
    }
}

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型のエイリアスを定義してエラー型を省略します。

サンプルコード

パターン1: enumカスタムエラー + From変換 + ? 演算子

sample_custom_error.rs
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 {}

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

type CalcResult<T> = Result<T, CalcError>;

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

fn parse_and_divide(a: &str, b: &str) -> CalcResult<i32> {
    let a: i32 = a.parse()?;
    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),
        }
    }

    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));
}
rustc custom_error.rs
./custom_error
10 / 2 = 5
エラー: ゼロ除算エラーです
20 / 4 = 5
abc / 4 エラー: 数値パースエラー: invalid digit found in string (ParseError(ParseIntError { kind: InvalidDigit }))
20 / 0 エラー: ゼロ除算エラーです (DivisionByZero)
Ok(42)
Err("シンプルなエラー")

パターン2: source()によるエラーチェーン

『Error』トレイトの『source()』メソッドを実装すると、元のエラーをチェーンとして保持できます。デバッグ時に根本原因を追跡できます。

error_chain.rs
use std::fmt;
use std::error::Error;
use std::num::ParseIntError;

#[derive(Debug)]
struct ScoreError {
    message: String,
    source: ParseIntError,
}

impl fmt::Display for ScoreError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "スコアのパースに失敗しました: {}", self.message)
    }
}

impl Error for ScoreError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(&self.source)
    }
}

fn parse_score(s: &str) -> Result<u32, ScoreError> {
    s.parse::<u32>().map_err(|e| ScoreError {
        message: format!("入力値: '{}'", s),
        source: e,
    })
}

fn main() {
    let inputs = vec!["100", "abc", "200"];
    for input in inputs {
        match parse_score(input) {
            Ok(score) => println!("score: {}", score),
            Err(ref e) => {
                println!("エラー: {}", e);
                if let Some(src) = e.source() {
                    println!("  原因: {}", src);
                }
            }
        }
    }
}
rustc error_chain.rs
./error_chain
score: 100
エラー: スコアのパースに失敗しました: 入力値: 'abc'
  原因: invalid digit found in string
score: 200

パターン3: 複数のエラー型をまとめるenumエラー(sampleキャラ情報の読み込み例)

アプリケーション内で複数種のエラーが発生しうる場合、それぞれのエラー型からの変換(From実装)をまとめたenumを用います。

multi_error.rs
use std::fmt;
use std::error::Error;
use std::num::ParseIntError;

#[derive(Debug)]
enum ProfileError {
    MissingField(String),
    InvalidPower(ParseIntError),
}

impl fmt::Display for ProfileError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ProfileError::MissingField(s) => write!(f, "フィールドがありません: {}", s),
            ProfileError::InvalidPower(e) => write!(f, "powerの値が不正です: {}", e),
        }
    }
}

impl Error for ProfileError {}

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

fn parse_profile(name: &str, power_str: &str) -> Result<String, ProfileError> {
    if name.is_empty() {
        return Err(ProfileError::MissingField(String::from("name")));
    }
    let power: u32 = power_str.parse()?;
    Ok(format!("{} (power: {})", name, power))
}

fn main() {
    let data = vec![
        ("user_a", "95"),
        ("", "88"),
        ("user_b", "abc"),
    ];
    for (name, power) in data {
        match parse_profile(name, power) {
            Ok(s) => println!("OK: {}", s),
            Err(e) => println!("エラー: {}", e),
        }
    }
}
rustc multi_error.rs
./multi_error
OK: user_a (power: 95)
エラー: フィールドがありません: name
エラー: powerの値が不正です: invalid digit found in string

概要

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

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

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

よくあるミス

ミス1: Display実装忘れでprintln!が使えない

カスタムエラー型に『fmt::Display』を実装していない場合、『println!("{}", e)』はコンパイルエラーになります。

ng_no_display.rs
#[derive(Debug)]
enum AppError {
    NotFound,
}

fn main() {
    let e = AppError::NotFound;
    println!("{}", e);
}
rustc ng_no_display.rs
error[E0277]: `AppError` doesn't implement `std::fmt::Display`

Display実装を追加すると解決します。

ok_no_display.rs
use std::fmt;

#[derive(Debug)]
enum AppError {
    NotFound,
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::NotFound => write!(f, "リソースが見つかりません"),
        }
    }
}

fn main() {
    let e = AppError::NotFound;
    println!("{}", e);
}
rustc ok_no_display.rs
./ok_no_display
リソースが見つかりません

ミス2: From実装忘れで ? 演算子が使えない

? 演算子でエラーを自動変換するには、対応する『From』トレイトの実装が必要です。実装がないとコンパイルエラーになります。

ng_no_from.rs
use std::fmt;
use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    ParseFailed,
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "パース失敗")
    }
}

fn parse_power(s: &str) -> Result<u32, AppError> {
    let n: u32 = s.parse()?; // ParseIntError → AppError への From がないためエラー
    Ok(n)
}
rustc ng_no_from.rs
error[E0277]: `?` couldn't convert the error to `AppError`
  = note: the trait `From<ParseIntError>` is not implemented for `AppError`

From<ParseIntError> を実装すると解決します。

ok_no_from.rs
use std::fmt;
use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
    ParseFailed(ParseIntError),
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::ParseFailed(e) => write!(f, "パース失敗: {}", e),
        }
    }
}

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

fn parse_power(s: &str) -> Result<u32, AppError> {
    let n: u32 = s.parse()?;
    Ok(n)
}

fn main() {
    println!("{:?}", parse_power("95"));
    println!("{:?}", parse_power("abc"));
}
rustc ok_no_from.rs
./ok_no_from
Ok(95)
Err(ParseFailed(ParseIntError { kind: InvalidDigit }))

ミス3: std::error::Errorトレイトの実装忘れ

『Box<dyn Error>』を返す関数や、エラーチェーンを使う場合は、カスタムエラー型に『std::error::Error』トレイトの実装が必要です。実装がないとコンパイルエラーになります。

ng_no_error_trait.rs
use std::fmt;

#[derive(Debug)]
struct AppError;

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "アプリエラー")
    }
}

fn get_error() -> Result<(), Box<dyn std::error::Error>> {
    Err(Box::new(AppError))
}
rustc ng_no_error_trait.rs
error[E0277]: the trait bound `AppError: std::error::Error` is not satisfied

『impl std::error::Error for AppError {}』を追加すると解決します。

ok_no_error_trait.rs
use std::fmt;

#[derive(Debug)]
struct AppError;

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "アプリエラー")
    }
}

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

fn get_error() -> Result<(), Box<dyn std::error::Error>> {
    Err(Box::new(AppError))
}

fn main() {
    match get_error() {
        Ok(_) => println!("OK"),
        Err(e) => println!("エラー: {}", e),
    }
}
rustc ok_no_error_trait.rs
./ok_no_error_trait
エラー: アプリエラー

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