【大家的项目】动态错误处理 crate: anyerr

anyerr 是一个动态错误处理库,提供了一个叫做 AnyError 的类型拥有表示错误,与 anyhow 类似但又有所不同,主要是在功能上更加丰富。大家如果喜欢,欢迎来 GitHub 来给个 star。 https://github.com/oosquare/anyerr

特性

anyerr 的各种特性可以总结为以下几点:

  • 错误包装:各错误处理库的基本功能。提供了 helper trait 来简化错误包装过程。

  • 错误类型:本 crate 中又称 error kind。AnyError 可以携带 error kind,而 error kind 上可定制的,可以使用 crate 中预定义的,也可以自定义。

  • 上下文信息:AnyError 可以携带除了 error message 和 error kind 以外的额外的上下文信息,一般表示为一系列 key-value pairs。储存上下文信息的数据结构也可以定制,本 crate 提供了具有不同的特性的变种。

  • 调用堆栈捕获:基本功能。如果 RUST_BACKTRACE 和 RUST_LIB_BACKTRACE 的值指定可以捕获堆栈,则必定会捕获。

安装

在 Cargo.toml 中添加

[dependencies]
anyerr = "0.1.1"

基本使用方法

定制自己的错误类型

上面提到了 AnyError 是可定制的,事实上,AnyError 是 AnyError<C, K>C 代表储存上下文的数据结构,K 是要使用的错误类型。

在自己的 crate 中使用 anyerr 时,一般会基于 AnyError<C, K> 来定制出自己的错误类型,然后在整个 crate 内使用。比如:

// Make this module accessible to your whole crate.
mod err {
    use anyerr::AnyError as AnyErrorTemplate;
    use anyerr::context::LiteralKeyStringMapContext; // A predefined context storage

    pub use anyerr::{Intermediate, Overlay}; // Helper traits.
    pub use anyerr::kind::DefaultErrorKind as ErrKind; // A predefined error kind.
    pub use anyerr::Report; // An error reporting utility.

    pub type AnyError = AnyErrorTemplate<LiteralKeyStringMapContext, ErrKind>;
    pub type AnyResult<T> = Result<T, AnyError>;
}

接下来使用的不再是 anyerr::AnyError,而是自己定制的 err::AnyError

错误的构造和使用

以下展示了如何构造一个 AnyError

use err::*;

fn fail() -> AnyResult<()> {
    // Use `AnyError::minimal()` to create a simple [`String`]-based error.
    Err(AnyError::minimal("this function always fails"))
}

fn check_positive(x: i32) -> AnyResult<()> {
    if x > 0 {
        return Ok(());
    }
    // Use `AnyError::quick()` to quickly create an error with an error
    // message and an error kind.
    Err(AnyError::quick(
        "expects `x` to be a positive number",
        ErrKind::ValueValidation
    ))
}

fn try_add_username(
    usernames: &mut Vec<String>,
    new_username: String
) -> AnyResult<usize> {
    let res = usernames.iter()
        .enumerate()
        .find(|(_, username)| **username == new_username)
        .map(|(index, _)| index);
    if let Some(index) = res {
        // Use `AnyError::builder()` to create an error with all essential
        // context you'll need.
        let err = AnyError::builder()
            .message("the username already exists")
            .kind(ErrKind::RuleViolation)
            .context("new_username", new_username)
            .context("index", index)
            .build();
        Err(err)
    } else {
        usernames.push(new_username);
        Ok(usernames.len() - 1)
    }
}

fn parse_i32(input: &str) -> AnyResult<i32> {
    // Use `AnyError::wrap()` to wrap any other error type.
    input.parse::<i32>().map_err(AnyError::wrap)
}

以上面的 try_add_username() 为例,假设我们拿到了其返回值,则我们可以这样处理:

use err::*;

fn main() {
    let mut usernames = Vec::new();

    let res = try_add_username(&mut usernames, "foo").unwrap();
    assert_eq!(res, 0);

    let err = try_add_username(&mut usernames, "foo").unwrap_err();
    assert_eq!(err.to_string(), "the username already exists"); // Or `err.message()`.
    assert_eq!(err.kind(), ErrKind::RuleViolation);
    assert_eq!(err.get("new_username"), Some("\"foo\""));
    assert_eq!(err.get("index"), Some("0"));
}

错误包装

以一个模拟访问数据库的代码片段为例子,展示如何使用 Overlay 和 Intermediate helper trait 来包装一个已有的错误。

use err::*;

struct UserRepository {
    conn: Arc<Connection>,
}

impl UserRepository {
    pub fn find_by_username(&self, username: &str) -> AnyResult<User> {
        // Don't build SQL statements yourself in practice.
        let statement = format!("SELECT * FROM users WHERE users.username = '{username}'");
        let data = self.conn.query(&statement)
            .overlay(("could not get a `User` due to SQL execution error", ErrKind::EntityAbsence))
            .context("username", username)
            .context("statement", statement)?;
        let entity = User::try_from(data)
            .overlay(("could not get a `User` due to serialization error", Errkind::EntityAbsence))
            .context("username", username)?;
        Ok(entity)
    }
}

Overlay::overlay() 用于一个 AnyError<C, K> 或 Result<T, AnyError<C, K>> 上,用于指定构造一个新的 AnyError 并将原有的错误包装进去,Overlay::overlay() 的参数可以传入 error message 和 error kind(具体看文档),然后其返回一个中间状态或者说是 builder,可以继续调用 Intermediate::context() 来添加上下文信息,最后借助 ? 传播包装后的错误。

错误报告

考虑如下这样嵌套多次的错误,借助 Report 可以方便地打印错误信息,无论是程序运行中打印到 logger 还是作为程序错误结束的报告,都是可以的:

use err::*;

fn source_error() -> AnyResult<()> {
    let err = AnyError::builder()
        .message("the source error is here")
        .kind(ErrKind::InfrastructureFailure)
        .context("key1", "value1")
        .context("key2", "value2")
        .build();
    Err(err)
}

fn intermediate_error() -> AnyResult<()> {
    source_error()
        .overlay("the intermediate error is here")
        .context("key3", "value3")?;
    Ok(())
}

fn toplevel_error() -> AnyResult<()> {
    intermediate_error()
        .overlay("the toplevel error is here")?;
    Ok(())
}

fn main() -> impl Termination {
    Report::capture(|| {
        toplevel_error()?;
        Ok(())
    })
}

完整示例

以下是一个超简单的 echo server 的例子,其中一些部分为了展示功能,可能会比正常写法略复杂:

mod err {
    use anyerr::context::LiteralKeyStringMapContext;
    use anyerr::AnyError as AnyErrorTemplate;

    pub use anyerr::kind::NoErrorKind as ErrKind;
    pub use anyerr::Report;
    pub use anyerr::{Intermediate, Overlay};

    pub type AnyError = AnyErrorTemplate<LiteralKeyStringMapContext, ErrKind>;
    pub type AnyResult<T> = Result<T, AnyError>;
}

use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::process::Termination;
use std::thread;
use std::time::Duration;

use err::*;

const SERVER_IP: &str = "127.0.0.1";
const SERVER_PORT: &str = "8080";

fn main() -> impl Termination {
    Report::capture(|| {
        let listener = TcpListener::bind(format!("{SERVER_IP}:{SERVER_PORT}"))
            .map_err(AnyError::wrap)
            .overlay("could not bind the listener to the endpoint")
            .context("ip", SERVER_IP)
            .context("port", SERVER_PORT)?;

        eprintln!("Started listening on {SERVER_IP}:{SERVER_PORT}");

        for connection in listener.incoming() {
            let Ok(stream) = connection else {
                continue;
            };

            thread::spawn(move || {
                handle_connection(stream).unwrap_or_else(|err| {
                    let report = Report::wrap(err).kind(false);
                    eprintln!("{report}");
                });
            });
        }

        Ok(())
    })
    .kind(false)
}

fn handle_connection(mut stream: TcpStream) -> AnyResult<()> {
    let client_addr = stream
        .peer_addr()
        .map_or("<UNKNOWN>".into(), |addr| addr.to_string());
    let mut buffer = [0u8; 256];
    let mut total_read = 0;

    eprintln!("{client_addr} started the connection");
    thread::sleep(Duration::from_secs(3));

    loop {
        let size_read = stream
            .read(&mut buffer)
            .map_err(AnyError::wrap)
            .overlay("could not read bytes from the client")
            .context("client_addr", &client_addr)
            .context("total_read", total_read)?;
        total_read  = size_read;

        if size_read == 0 {
            eprintln!("{client_addr} closed the connection");
            return Ok(());
        }

        thread::sleep(Duration::from_secs(3));

        let mut cursor = 0;
        while cursor < size_read {
            let size_written = stream
                .write(&buffer[cursor..size_read])
                .map_err(AnyError::wrap)
                .overlay("could not write bytes to the client")
                .context("client_addr", &client_addr)
                .context("total_read", total_read)
                .context("cursor", cursor)?;
            cursor  = size_written;
        }
    }
}

进阶用法

更多用法和 API 请参考 Docs.rs。

标题“51单片机通过MPU6050-DMP获取姿态角例程”解析 “51单片机通过MPU6050-DMP获取姿态角例程”是一个基于51系列单片机(一种常见的8位微控制器)的程序示例,用于读取MPU6050传感器的数据,并通过其内置的数字运动处理器(DMP)计算设备的姿态角(如倾斜角度、旋转角度等)。MPU6050是一款集成三轴加速度计和三轴陀螺仪的六自由度传感器,广泛应用于运动控制和姿态检测领域。该例程利用MPU6050的DMP功能,由DMP处理复杂的运动学算法,例如姿态融合,将加速度计和陀螺仪的数据进行整合,从而提供稳定且实时的姿态估计,减轻主控MCU的计算负担。最终,姿态角数据通过LCD1602显示屏以字符形式可视化展示,为用户提供直观的反馈。 从标签“51单片机 6050”可知,该项目主要涉及51单片机和MPU6050传感器这两个关键硬件组件。51单片机基于8051内核,因编程简单、成本低而被广泛应用;MPU6050作为惯性测量单元(IMU),可测量设备的线性和角速度。文件名“51-DMP-NET”可能表示这是一个与51单片机及DMP相关的网络资源或代码库,其中可能包含C语言等适合51单片机的编程语言的源代码、配置文件、用户手册、示例程序,以及可能的调试工具或IDE项目文件。 实现该项目需以下步骤:首先是硬件连接,将51单片机与MPU6050通过I2C接口正确连接,同时将LCD1602连接到51单片机的串行数据线和控制线上;接着是初始化设置,配置51单片机的I/O端口,初始化I2C通信协议,设置MPU6050的工作模式和数据输出速率;然后是DMP配置,启用MPU6050的DMP功能,加载预编译的DMP固件,并设置DMP输出数据的中断;之后是数据读取,通过中断服务程序从DMP接收姿态角数据,数据通常以四元数或欧拉角形式呈现;再接着是数据显示,将姿态角数据转换为可读的度数格
MathorCup高校数学建模挑战赛是一项旨在提升学生数学应用、创新和团队协作能力的年度竞赛。参赛团队需在规定时间内解决实际问题,运用数学建模方法进行分析并提出解决方案。2021年第十一届比赛的D题就是一个典型例子。 MATLAB是解决这类问题的常用工具。它是一款强大的数值计算和编程软件,广泛应用于数学建模、数据分析和科学计算。MATLAB拥有丰富的函数库,涵盖线性代数、统计分析、优化算法、信号处理等多种数学操作,方便参赛者构建模型和实现算法。 在提供的文件列表中,有几个关键文件: d题论文(1).docx:这可能是参赛队伍对D题的解答报告,详细记录了他们对问题的理解、建模过程、求解方法和结果分析。 D_1.m、ratio.m、importfile.m、Untitled.m、changf.m、pailiezuhe.m、huitu.m:这些是MATLAB源代码文件,每个文件可能对应一个特定的计算步骤或功能。例如: D_1.m 可能是主要的建模代码; ratio.m 可能用于计算某种比例或比率; importfile.m 可能用于导入数据; Untitled.m 可能是未命名的脚本,包含临时或测试代码; changf.m 可能涉及函数变换; pailiezuhe.m 可能与矩阵的排列组合相关; huitu.m 可能用于绘制回路图或流程图。 matlab111.mat:这是一个MATLAB数据文件,存储了变量或矩阵等数据,可能用于后续计算或分析。 D-date.mat:这个文件可能包含与D题相关的特定日期数据,或是模拟过程中用到的时间序列数据。 从这些文件可以推测,参赛队伍可能利用MATLAB完成了数据预处理、模型构建、数值模拟和结果可视化等一系列工作。然而,具体的建模细节和解决方案需要查看解压后的文件内容才能深入了解。 在数学建模过程中,团队需深入理解问题本质,选择合适的数学模
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值