anyhow = “1.0.97”
https://docs.rs/anyhow/latest/anyhow/
anyhowを使うと、エラー処理が簡単にできるようになる。
anyhow::Errorはエラー型のラッパー、あらゆるエラー型からanyhow::Errorに変換できる。変換できる。
Resultにすると、?演算子で異なる複数の型のエラーを呼び出し元に移譲できる。
contextやwith_contextを使うと、Noneをanyhow::Errorとして移譲することも可能
anyhow::ResultがResultの型エイリアスとして定義されているので、use anyhow::Result;とすると、ResultをResultと書ける。
use anyhow::{ensure, Context, Result};
use std::fs;
#[tokio::main]
async fn main() {
let path_123 = "./data/temp/123.txt";
println!("{}", with_unwrap(&path_123));
// let path_abc = "./data/temp/abc.txt";
// println!("{}", with_unwrap(&path_abc));
let path_non_existent = "./data/to/non_existent.txt";
println!("{}", with_unwrap(&path_non_existent));
}
fn with_unwrap(path: &str) -> i32 {
let s = fs::read_to_string(path).unwrap();
let i = s.parse::<i32>().unwrap();
i * 10
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.76s
Running `target/debug/app`
1230
thread ‘main’ panicked at src/main.rs:16:30:
called `Result::unwrap()` on an `Err` value: ParseIntError { kind: InvalidDigit }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: “No such file or directory” }
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
vagrant@vagrant:~/dev/rust/app$
エラーメッセージが、InvalidDigit、No such file or directoryでそれぞれ異なる。
#[tokio::main]
async fn main() {
let path_123 = "./data/temp/123.txt";
println!("{}", with_unwrap(&path_123).unwrap());
let path_abc = "./data/temp/abc.txt";
println!("{:?}", with_unwrap(&path_abc).unwrap_err());
let path_non_existent = "./data/to/non_existent.txt";
println!("{:?}", with_unwrap(&path_non_existent).unwrap_err());
}
fn with_unwrap(path: &str) -> Result<i32> {
let s = fs::read_to_string(path)?;
let i = s.parse::<i32>()?;
Ok(i * 10)
}
1230
invalid digit found in string
No such file or directory (os error 2)
### anyhow::Contextでコンテキストを追加
with_contextは遅延評価でエラーの時のみ指定
fn with_unwrap(path: &str) -> Result<i32> {
let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
let i = s.parse::<i32>()
.with_context(|| format!("Failed to parse {s:?}"))?;
Ok(i * 10)
}
1230
Failed to parse “abc”
Caused by:
invalid digit found in string
Failed to read “./data/to/non_existent.txt”
Caused by:
No such file or directory (os error 2)
### OptionをResultに変換
contextおよび with_contextはResultだけでなく、Optionからも呼べる。Noneも呼び出し元に移譲できる。
async fn main() {
let path_max = "data/temp/max.txt";
fs::write(path_max, i32::MAX.to_string()).unwrap();
println!("{:?}", with_anyhow_option(path_max).unwrap_err());
let path_123 = "./data/temp/123.txt";
println!("{}", with_unwrap(&path_123).unwrap());
let path_abc = "./data/temp/abc.txt";
println!("{:?}", with_unwrap(&path_abc).is_err());
let path_non_existent = "./data/to/non_existent.txt";
println!("{:?}", with_unwrap(&path_non_existent).is_err());
}
fn with_anyhow_option(path: &str) -> Result<i32> {
let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
let i = s.parse::<i32>()
.with_context(|| format!("Failed to parse {s:?}"))?;
let i = i.checked_add(100).context("Overflow occurred")?;
Ok(i)
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.86s
Running `target/debug/app`
Overflow occurred
1230
true
true
### マクロ(anyhow!, bail!, ensure!)で単発のエラーを返す
ensureで値に応じてエラーとすることもできる。
fn with_anyhow_macro(path: &str) -> Result<i32> {
let s = fs::read_to_string(path).with_context(|| format!("Failed to read {path:?}"))?;
let i = s.parse::<i32>()
.with_context(|| format!("Failed to parse {s:?}"))?;
let i = i.checked_add(100).context("Overflow occurred")?;
ensure!(i >= 1000, "Value must be at least 1000, got {}", i);
Ok(i)
}
match resultでの処理
let path_123 = "./data/temp/123.txt";
let result = with_anyhow_macro(&path_123);
match result {
Ok(n) => {
println!("{}", n);
}
Err(error) => {
println!("{}", error);
}
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.81s
Running `target/debug/app`
Value must be at least 1000, got 223