Rustのエラーハンドリングを楽にするanyhowの使い方
Rustでいろいろなエラー型のエラー処理が大変だなと感じる場合は、dtolnay/anyhow を使うことで簡単にエラーハンドリングをシンプルにすることができます。 この記事では、anyhowの主要な使い方を3つ紹介しながら、どれだけ簡単にエラー処理を実装できるかを説明していきます。ちなみに、詳細までは説明しないので、知りたい方はREADMEをご覧ください。
anyhowの概要
はじめにanyhow
の特徴について簡単に説明します。
std::result::Result<T, E>
型を使いやすくしたanyhow::Result<T>
型が便利context()
やwith_context()
でエラーに簡単にコンテキスト情報を付与できるanyhow!
やbail!
マクロでanyhow::Result<T>
型のエラーを簡単に作成できる
anyhow::Result型が便利
anyhow::Result
は、次のようにstd::result::Result
のE
を省略可能な形で定義されています。
pub type Result<T, E = Error> = Result<T, E>;
そのため、以下のようにanyhow::Result
を使うことでResult
を簡単に記載することができるようになります。
// anyhow::Result を使わない場合
fn maybe_failure() -> Result<(), SomethingError> { ... }
// anyhow::Result を使う場合
use anyhow::Result;
fn maybe_failure() -> Result<()> { ... }
// ^ fn maybe_failure() -> std::result::Result<T, anyhow::Error> { ... } と同様
// anyhow::Result でErrorの型を指定することも可能
fn maybe_failure() -> Result<(), SomethingError> { ... }
// ^ fn maybe_failure() -> std::result::Result<T, SomethingError> { ... } と同様
また、anyhow::Error
はBox<dyn std::error::Error>
のように振る舞います。そのため、以下のようにさまざまなエラータイプを返す関数において、?
を使って見通しがよいコードが書けます。
use anyhow::Result;
use serde_json::Value;
fn main() -> Result<()> {
// ファイルが見つからない場合 std::io::Error を返す
let config = std::fs::read_to_string("cluster.json")?;
// ファイル内の文字列をJSONにパースできない場合 serde_json::Error を返す
let map: Value = serde_json::from_str(&config)?;
println!("cluster info: {:#?}", map);
Ok(())
}
context()やwith_context()でエラーにコンテキスト情報を付与
context()
やwith_context()
を使うことでエラーにコンテキスト情報を付与できます。
それぞれの違いは、context
はエラーにコンテキスト情報を付与、with_context()
はエラー発生時のみ遅延評価されコンテキスト情報を付与します。
use anyhow::{Context, Result};
use std::fs;
use std::path::PathBuf;
pub struct ImportantThing {
path: PathBuf,
}
pub fn do_it(it: ImportantThing) -> Result<Vec<u8>> {
let path = &it.path;
# with_context()でエラー時にコンテキスト情報を付与する
let content = fs::read(path)
.with_context(|| format!("Failed to read instrs from {}", path.display()))?;
Ok(content)
}
fn main() -> Result<()> {
let it = ImportantThing {
path: PathBuf::from("instructions.txt"),
};
let content = do_it(it)?;
println!("{:?}", content);
Ok(())
}
上記のコードを実行すると以下のようなコンテキスト付きのエラーメッセージが表示されます。
Error: Failed to read instrs from instructions.txt
Caused by:
No such file or directory (os error 2)
anyhow!やbail!マクロでanyhow::Result<T>
型のエラーを作成
anyhow::Result
型を簡単に作成するanyhow!
やbail!
マクロがあります。
use anyhow::{anyhow, Result};
fn validate(key: &str) -> Result<()> {
if key.len() != 16 {
return Err(anyhow!("key length must be 16 characters, got {:?}", key));
}
Ok(())
}
fn main() {
let key = "1234567890";
let err = validate(key).unwrap_err();
println!("Validation Error: {}", err);
// Validation Error: key length must be 16 characters, got "1234567890"
}
また、bail!
を使うとreturn Err(anyhow!(...)
と書く必要がなります。
use anyhow::{bail, Result};
fn validate(key: &str) -> Result<()> {
if key.len() != 16 {
bail!("key length must be 16 characters, got {:?}", key);
}
Ok(())
}
fn main() {
let key = "1234567890";
let err = validate(key).unwrap_err();
println!("Validation Error: {}", err);
// Validation Error: key length must be 16 characters, got "1234567890"
}