Rustで良さげなエラーメッセージを出力 w/ anyhow, thiserror


CLIのツールを作るときに、Rustのanyhow, thiserrorを使ってこう書いとけばとりあえず困らなそう、というところまで来たのでメモとして記します。

Cargo.toml

Cargo.tomlのdependenciesにanyhowthiserrorを追加します。

[dependencies]
anyhow = "1.0"
thiserror = "1.0"

main.rs

main.rsでは、冒頭にanyhow, thiserrorcontexterrorを使えるように次のように書いておきます。

use anyhow::{self, Context};
use thiserror::Error;

自前のエラーはthiserror::Errorを用いて、エラーメッセージのフォーマットとともに定義します。「My...」や「my ...」等は実際のアプリケーションの名前や実際のエラーメッセージ等に置き換えます。

#[derive(Error, Debug)]
pub enum MyAppError {
    #[error("line {}: my decode error", .linenum)]  // 付加的な情報を持つエラーメッセージの場合
    MyDecodeError { linenum: usize },
    
    #[error("my fuga error")] // 付加的な情報を持たないエラーメッセージの場合
    MyFugaError,
}

main関数はanyhow::Result<()>を戻り値にするものに決め打ちします。

fn main() -> anyhow::Result<()> {
    
    ....
    
    Ok(())
}

(ここでanyhow::Resultとしているのは、単にResutとした場合には、あとでuseを足したときにstd::result::Resultstd::io::Resultなどを指してしまうことがあるので、あらかじめ明示しておいてそれを避けるためです。)

条件判定してエラーにする場合

自前で定義したエラーを作り.into()anyhow::Errorに変換するような書き方にします。

if 条件 {
    return Err(MyAppError::MyDecodeError { linenum: li + 1 }.into())?;
}
if 条件 {
    return Err(MyAppError::MyFugaError.into())?;
}

ファイル読み込みなどのエラーから、自前のエラーに変換する場合

.unrwap()を使うとpanicしたようなエラーメッセージが出力されてしまうので、context, with_contextを使って書いていきます。

let fp = File::open(input_file).with_context(|| format!("fail to open file: {}", input_file))?;
for (li, line) in io::BufReader::new(fp).lines().enumerate() {
    let line = line.context(MyAppError::MyDecodeError { linenum: li + 1 })?;
    
    ....

}

さらに短く書きたい!

(1) anyhow!マクロを使うとその場でエラーを生成できます。1箇所でしか起きないエラーなら、anyhow!マクロを使って生成するとお手軽です。

return Err(anyhow!("given value is too large {}", value));

その場合は、main.rsの冒頭でマクロを使うことを明示します。

#[macro_use]
extern crate anyhow;

use anyhow::Context;
use thiserror::Error;

(2) thiserrorでエラーを定義するときに、値に名前を付けなくてよければタプルを使うことができます。

#[derive(Error, Debug)]
pub enum MyAppError {
    #[error("line {}: my decode error", .0)]  // 付加的な情報を持つエラーメッセージの場合
    MyDecodeError(usize),
}
let line = line.context(MyAppError::MyDecodeError(li + 1))?;

追記