首页 > 其他分享 >【转】Rust anyhow & thiserror

【转】Rust anyhow & thiserror

时间:2023-08-14 13:34:31浏览次数:50  
标签:std use error source Error anyhow Rust thiserror

Rust 中使用 std::result::Result 表示可能出错的操作,成功的时候是 Ok(T),而出错的时候则是 Err(E)

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

通常情况下,E 是实现 std::error::Error 的错误类型:

pub trait Error: Debug + Display {
    fn source(&self) -> Option<&(dyn Error + 'static)> { ... }
    fn backtrace(&self) -> Option<&Backtrace> { ... }
    fn description(&self) -> &str { ... }
    fn cause(&self) -> Option<&dyn Error> { ... }
}

我们通常也需要在自己的代码中自定义错误,并且为之手动实现 std::error::Error,这个工作很麻烦,所以就有了 thiserror,自动帮我们生成实现的 std::error::Error 的代码。

而借助于 anyhow::Error,和与之对应的 Result<T, anyhow::Error>,等价于 anyhow::Result<T>,我们可以使用 ? 在可能失败的函数中传播任何实现了 std::error::Error 的错误。

 

thiserror

可以使用命令 cargo add thiserror 将它添加到自己的项目中,或者在 Cargo.toml 中添加如下的配置:

[dependencies]
thiserror = "1.0"

thiserror 可以用于枚举或者结构体,例如,我们来看一个基本的例子:

use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("data store disconnected")]
    Disconnect(#[from] std::io::Error),
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
    #[error("invalid header (expected {expected:?}, found {found:?})")]
    InvalidHeader { expected: String, found: String },
    #[error("unknown data store error")]
    Unknown,
}

 

#[error]

如果使用 #[error(...)] 为结构体或者枚举生成自定义错误消息,这将为它们实现 Display

use thiserror::Error;

#[derive(Error, Debug)]
#[error("something failed, msg is: {msg}")]
pub struct MyError {
    msg: &'static str,
}

我们可以在错误中插入字段的简写,一共有四种形式:

#[error("{var}")] <=> write!("{}", self.var)
#[error("{0}")]  <=> write!("{}", self.0)
#[error("{var:?}")] <=> write!("{:?}", self.var)
#[error("{0:?}")]  <=> write!("{:?}", self.0)

例如:

use thiserror::Error;

pub fn first_char(s: &String) -> char {
    if s.len() == 0 {
        '-'
    } else {
        s.chars().next().unwrap_or('-')
    }
}

#[derive(Debug)]
pub struct Limits {
    lo: i16,
    hi: i16,
}

#[derive(Error, Debug)]
pub enum Error {
    #[error("invalid rdo_lookahead_frames {0} (expected < {})", i32::MAX)]
    InvalidLookahead(u32),
    #[error("first letter must be lowercase but was {:?}", first_char(.0))]
    WrongCase(String),
    #[error("invalid index {idx}, expected at least {} and at most {}", .limits.lo, .limits.hi)]
    OutOfBounds { idx: usize, limits: Limits },
}

 

#[from]

可以使用 #[from] 注解为错误类型实现 From,可以从其他错误生成:

#![allow(unused)]
#![feature(backtrace)]

use std::backtrace;
use std::error::Error as _;
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
#[error("some io error happened, {:?}", .source)]
pub struct MyError {
    #[from]
    source: io::Error,
    backtrace: backtrace::Backtrace,
}

fn main() {
    let err = MyError::from(io::Error::from(io::ErrorKind::TimedOut));
    println!("{:?}", err.source());
}

 

#[source]

可以使用 #[source] 属性,或者将字段命名为 source,可为自定义错误实现 source 方法,返回底层的错误类型:

use std::error::Error;
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("some io error happened, {:?}", .source)]
    IO { source: io::Error },
}

fn main() {
    let err = MyError::IO {
        source: io::Error::from(io::ErrorKind::TimedOut),
    };
    println!("{:?}", err.source());
}

或者使用 #[source] 属性标记非 source 的字段,例如:这里是 err 字段:

use std::error::Error;
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("some io error happened, {:?}", .err)]
    IO {
        #[source]
        err: io::Error,
    },
}

fn main() {
    let err = MyError::IO {
        err: io::Error::from(io::ErrorKind::TimedOut),
    };
    println!("{:?}", err.source());
}

#[from] 和 #[source] 二选一即可,#[from] 也会为类型生成 .source() 方法,例如:

#![allow(unused)]
#![feature(backtrace)]

use std::backtrace;
use std::error::Error as _;
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
#[error("some io error happened, {:?}", .source)]
pub struct MyError {
    #[from]
    source: io::Error,
    backtrace: backtrace::Backtrace,
}

fn main() {
    let err = MyError::from(io::Error::from(io::ErrorKind::TimedOut));
    println!("{:?}", err.source());
}

 

#[backtrace]

只要在我们的错误结构体里面放个类型为 std::backtrace::Backtrace 的字段,就会自动实现 backtrace() 方法,可以看 #[from]

另外,如果使用 #[backtrace] 标记 sourcesource 字段,或者 #[source],或者 #[from]),那么 backtrace() 方法会转发到 source 的 backtrace

文档里面的例子(没理解,以后再来改):

#[derive(Error, Debug)]
pub enum MyError {
    Io {
        #[backtrace]
        source: io::Error,
    },
}

 

#[error(transparent)]

可以通过 #[error(transparent)] 让 source 和 Display 直接使用底层的错误,这对于那些想处理任何的枚举来说是很有用的:

#![allow(unused)]

use anyhow::anyhow;
use std::error::Error as _;
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
#[error(transparent)]
pub struct MyError {
    #[from]
    source: anyhow::Error,
}

fn main() {
    let err = MyError::from(anyhow!("Missing attribute: {}", "field1"));
    println!("{:?}", err);
}
#![allow(unused)]

use anyhow::anyhow;
use std::error::{self, Error as _};
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum MyError {
    #[error("file not found")]
    FileNotFound,
    #[error(transparent)]
    Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error
}

fn main() {
    let err = MyError::from(anyhow!("Missing attribute: {}", "field1"));
    println!("{:?}", err);
}

 

anyhow

anyhow::Error 是这个 crate 中最重要的结构体,它是动态错误类型的包装器,能从所有实现了 std::error::Error + Send + Sync + 'static 的错误转换而来,也能转换成 Box<dyn std::error::Error + Send + Sync + 'static>,它有以下特点:

  1. anyhow::Error 要求包裹的错误必须是 Send + Sync + 'static
  2. anyhow::Error 保证 backtrace 是可用的,就是底层的错误类型没有提供;
  3. anyhow::Error 在内存中只占一个机器字而不是两个;

如果我们要将 anyhow::Error 以文本形式展出来,可以有下面几种形式:

  1. 可以使用 {} 或者 .to_string(),但是仅仅打印最外层错误或者上下文,而不是内层的错误;

  2. 可以使用 {:#} 打印外层和底层错误;

  3. 可以使用 {:?} 在调试模式打印错误以及调用栈;

  4. 可以使用 {:#?} 以结构体样式打印错误,例如:

  5. Error {
        context: "Failed to read instrs from ./path/to/instrs.json",
        source: Os {
            code: 2,
            kind: NotFound,
            message: "No such file or directory",
        },
    }

另外,既然 anyhow::Error 包装了底层的错误,那就得提供找到内层错误的方法,这里是 downcast_ref

#![allow(unused)]

use anyhow::{anyhow, bail};
use std::io;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
}

fn foo() -> anyhow::Result<()> {
    // 使用 ?运算符能将任何实现了 std::error::Error + Send + Sync + 'static 的错误转换为 anyhow::Error
    std::fs::read_to_string("config.json")?;
    Ok(())
}

fn main() {
    match foo() {
        Ok(()) => (),
        Err(ref root_cause) => {
            let err = root_cause.downcast_ref::<DataStoreError>();
            match err {
                Some(DataStoreError::Redaction(_)) => (),
                None => (),
            }
            println!("{:#?}", root_cause);
        }
    }
}

 

anyhow!

使用 anyhow! 这个宏可以生成 anyhow::Error类型的值,它可以接受字符串,格式化字符串作为参数,或者实现 std::error:Error 的错误作为参数。

use anyhow::{anyhow, Result};

fn lookup(key: &str) -> Result<V> {
    if key.len() != 16 {
        return Err(anyhow!("key length must be 16 characters, got {:?}", key));
    }

    // ...
}

或者从实现了 std::error::Error 的错误转换而来:

use anyhow::anyhow;
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
}

fn bar() -> std::result::Result<(), DataStoreError> {
    Err(DataStoreError::Redaction("".to_owned()))
}

fn foo1() -> anyhow::Result<()> {
    let a = bar()?;
    Ok(())
}

fn foo2() -> anyhow::Result<()> {
    Err(anyhow::Error::from(DataStoreError::Redaction(
        "".to_string(),
    )))
}

fn foo3() -> anyhow::Result<()> {
    Err(anyhow!(DataStoreError::Redaction("".to_owned())))
}

fn main() {}

又或者:

use anyhow::anyhow;

fn foo() -> anyhow::Result<()> {
    Err(anyhow!("missing {} field", "f1"))
}

 

bail!

anyhow::bail 宏用于提前错误返回,它等价于 return Err(anyhow!($args...)),包含这个宏的函数的返回值必须是 Result<_,anyhow::Error>

use anyhow::{anyhow, bail};
use thiserror::Error;

#[derive(Error, Debug)]
pub enum DataStoreError {
    #[error("the data for key `{0}` is not available")]
    Redaction(String),
}

fn foo(i: i16) -> anyhow::Result<()> {
    if i < 0 {
        bail!(DataStoreError::Redaction("something wrong".to_string()));
    }
    Ok(())
}

 

anyhow::Context

anyhow::Context 为 anyhow::Result 类型提供了 context 方法,能在错误发生时提供更多的上下文信息:

use anyhow::{anyhow, Context, Result};
use std::fs;
use std::path::PathBuf;

pub struct ImportantThing {
    path: PathBuf,
}

impl ImportantThing {
    pub fn detach(&mut self) -> Result<()> {
        Err(anyhow!("detach faield"))
    }
}

pub fn do_it(mut it: ImportantThing) -> Result<Vec<u8>> {
    it.detach()
        .context("Failed to detach the important thing")?;

    let path = &it.path;
    let content =
        fs::read(path).with_context(|| format!("Failed to read instrs from {}", path.display()))?;

    Ok(content)
}

fn main() {
    let mut it = ImportantThing {
        path: PathBuf::new(),
    };
    match do_it(it) {
        Ok(_) => (),
        Err(ref err) => {
            for cause in err.chain() {
                println!("{}", cause);
            }
        }
    }
}

这段代码将输出:

Failed to detach the important thing
detach faield

对于下面的代码也是输出:

pub fn do_it(it: &mut ImportantThing) -> Result<Vec<u8>> {
    let path = &it.path;
    let content =
        fs::read(path).with_context(|| format!("Failed to read instrs from {}", path.display()))?;

    Ok(content)
}

fn main() {
    let mut it = ImportantThing {
        path: PathBuf::new(),
    };
    match do_it(&mut it) {
        Ok(_) => (),
        Err(ref err) => {
            for cause in err.chain() {
                println!("{}", cause);
            }
        }
    }
}

将输出:

Failed to read instrs from 
No such file or directory (os error 2)

 

 

Reference:

https://blog.fudenglong.site/2022/05/11/Rust/anyhow-and-thiserror/ => 原文 【Rust】anyhow & thiserror

标签:std,use,error,source,Error,anyhow,Rust,thiserror
From: https://www.cnblogs.com/piperck/p/17628383.html

相关文章

  • 《Rust编程之道》学习笔记一
    《Rust编程之道》学习笔记一序Rust语言的主要特点系统级语言无GC基于LLVM内存安全强类型+静态类型混合编程范式零成本抽象线程安全程序员的快乐何谓快乐?真正的快乐不仅仅是写代码时的“酸爽”,更应该是代码部署到生产环境之后的“安稳”。程序的三大定律程序必须......
  • 文盘Rust -- Mutex解决并发写文件乱序问题
    在实际开发过程中,我们可能会遇到并发写文件的场景,如果处理不当很可能出现文件内容乱序问题。下面我们通过一个示例程序描述这一过程并给出解决该问题的方法。usestd::{fs::{self,File,OpenOptions},io::{Write},sync::Arc,time::{SystemTime,UNIX_EPOCH}......
  • 试试用Rust为树莓派RP2040开发程序
    试试用Rust为树莓派RP2040开发程序实验环境树莓派Pico开发板DAPLINK调试器原工程链接:https://github.com/rp-rs/rp-hal-boards假设读者已经安装配置好了cargo环境1.安装配置获取工具链rustupselfupdaterustupupdatestablerustuptargetaddthumbv......
  • Python潮流周刊#2:Rust 让 Python 再次伟大
    这里记录每周值得分享的Python及通用技术内容,部分为英文,已在小标题注明。(本期标题取自其中一则分享,不代表全部内容都是该主题,特此声明。)文章&教程1、Python修饰器的函数式编程介绍了装饰器的实现原理、带参装饰器、多装饰器、类装饰器和几个典型的示例。文章发布于2014年,代码用......
  • vscode 运行Rust cargo test时显示log输出
    使用以下tasks.json对于log库的输出(info,debug,warn...)需要在test方法上一行加#[test_log::test](来自test-loghttps://crates.io/crates/test-log){"version":"2.0.0","tasks":[{"type":"shell&quo......
  • Rust交叉编译为Android库
    Rust目前在互联网上资料较少,经过几天的折腾,终于在Windows10和GithubActions(Ubuntu)上构建出了armv7和armv8的.so文件。关于JNI的配置见:【Rust实现JNI】https://juejin.cn/post/7092750468631740452交叉编译需要AndroidNDK,我目前使用的是android-ndk-r25c......
  • xposed优秀模块列表 --- trustmealready
    https://repo.xposed.info/module/com.virb3.trustmealready一个Xposed模块,使用MattiaVinci提供的出色技术在Android上禁用SSL验证和固定。效果是系统范围的。适用于各种安全审计。 作者: ViRb3支持/讨论网址: https://github.com/ViRb3/TrustMeAlready源代码网址: https......
  • Rust + Tauri 开发一个自动生成申论的桌面应用
    前端开发桌面应用,第一反应肯定是 Electron但Electron有一个众所周知的问题:每一个应用都会打包一个 chromium。如果电脑上安装了10个Electron应用,就会安装10个chromium而Tauri使用 WebView作为GUI方案,不会打包在应用内,而是检查系统是否有预装WebView,从而避免多个应......
  • Rust 在Window上交叉编译Android库问题 error: linking with
    报错:error:linkingwith`D:/NDK/android-ndk-r25c/toolchains/llvm/prebuilt/windows-x86_64/bin/aarch64-linux-android30-clang.cmd`failed:exitcode:255|=note:"D:/NDK/android-ndk-r......
  • 解决在macOS系统上使用rust-gdb调式rust代码时无法进入断点的问题
        问题title缩写,主要原因是gdb无法在cargo生成可执行文件和符号信息关联起来,类型信息如下图:  解决方案:在Cargo.toml文件中添加一项配置,所以能找到符号信息. 配置信息说明:1. profile.dev或者profile.release是用cargobuild进行编译时使用到的配置......