Rustで良さげなエラーメッセージを出力 w/ anyhow, thiserror
CLIのツールを作るときに、Rustのanyhow, thiserrorを使ってこう書いとけばとりあえず困らなそう、というところまで来たのでメモとして記します。
Cargo.toml
Cargo.tomlのdependenciesにanyhowとthiserrorを追加します。
[dependencies]
anyhow = "1.0"
thiserror = "1.0"
main.rs
main.rsでは、冒頭にanyhow, thiserrorのcontextやerrorを使えるように次のように書いておきます。
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::Resultやstd::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))?;
追記
- anyhowに関してより詳しい記事を見つけました (2022.04.02)
Rust/AnyhowのTips https://zenn.dev/yukinarit/articles/b39cd42820f29e
Discussion