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" }