Anyhow Error Handler
Библиотека работы с ошибками - https://docs.rs/anyhow/latest/anyhow/
- Использует единый тип Error для всех ошибок;
- Добавляет контекст к ошибкам;
- Поддержка backtrace для debug;
Установка
cargo add anyhow
Использование
Anyhow создаёт алиас наподобие Result<T> = Result<T, Box<dyn Error>>
, чтобы скрыть тип ошибок и сделать его универсальным.
// ---- Без Anyhow
fn string_error() -> Result<(), String> {
Ok(())
}
fn io_error() -> Result<(), std::io::Error> {
Ok(())
}
fn any_error() -> Result<(), Box<dyn Error>> {
string_error()?;
io_error()?;
Ok(())
}
// ---- С Anyhow:
use anyhow::Result;
fn string_error() -> Result<()> {
Ok(())
}
fn io_error() -> Result<()> {
Ok(())
}
fn any_error() -> Result<()> {
string_error()?;
io_error()?;
Ok(())
}
Пример неудачного чтения файла:
use anyhow::{Context, Result};
fn read_config_file(path: &str) -> Result<String> {
std::fs::read_to_string(path).with_context(|| format!("Failed to read file {}", path))
}
fn main() -> Result<()> {
let config_content = read_config_file("conf.txt")?;
println!("Config content:\n{:?}", config_content);
Ok(())
}
Result<T>
становится алиасом кResult<T, anyhow::Error>
;Context
,with_context()
позволяет добавить подробности к ошибке, в случае неуспеха функции чтенияread_to_string()
;- Оператор
?
выносит ошибку вверх, при этом авто-конвертирует её тип вanyhow::Error
.
Замена Box<dyn>
с контекстом
Возьмём пример, в котором чтение файла std::fs::read_to_string()
(может быть неудачным), далее дешифровка его контента с помощью base64 decode()
(может не получиться) в цепочку байт, из которой формируется строка String::from_utf8()
(может не получиться). Все эти три потенциальных ошибки имеют разный тип.
Один способ все три их принять на одну функцию, это с помощью Box<dyn std::error::Error>>
, потому что все 3 ошибки применяют std::error::Error
.
use base64::{self, engine, Engine};
fn decode() -> Result<(), Box<dyn std::error::Error>> {
let input = std::fs::read_to_string("input")?;
for line in input.lines() {
let bytes = engine::general_purpose::STANDARD.decode(line)?;
println!("{}", String::from_utf8(bytes)?);
}
Ok(())
}
Подход рабочий, но при срабатывании одной из трёх ошибок, судить о происхождении проблемы можно будет лишь по сообщению внутри ошибки.
В случае применения Anyhow, можно заменить им Box<dyn>
, при этом сразу добавить контекстные сообщения, которое поможет понять место:
use anyhow::Context;
use base64::{self, engine, Engine};
fn decode() -> Result<(), anyhow::Error> {
let input = std::fs::read_to_string("input")
.context("Failed to read file")?; // контекст 1
for line in input.lines() {
let bytes = engine::general_purpose::STANDARD
.decode(line)
.context("Failed to decode the input")?; // контекст 2
println!(
"{}",
String::from_utf8(bytes)
.context("Failed to cenvert bytes")? // контекст 3
);
}
Ok(())