Rust 错误处理

rust · 浏览次数 : 0

小编点评

**使用 Result<T, E> 处理错误** `Result<T, E>` 是一种用于表示结果的类型,其中 `T` 代表结果类型,而 `E` 代表错误类型。它允许你将 `Result` 转换为 `T`,并提供有关错误来源的信息。 **将错误信息添加到异常中** 为了方便处理错误,你可以将错误信息添加到异常中。你可以使用 `Err` 类型将 `MyError` 类型包装成 `Result`,并在 `try`块中使用 `match` 语句处理结果。 **自定义错误类型** `MyError` 是一种自定义类型,它包含文件路径信息。这有助于你快速识别缺失的文件。 **示例** ```rust use std::error::Error; use std::fmt; use std::fmt::Display; #[derive(Debug)] pub enum MyError { FileOpenError(String), ParseError(String), Common(String), } impl Error for MyError {} impl Display for MyError {} fn read_file_to_string(file_path: String) -> Result { let mut file = File::open(file_path.clone()); match file { Ok(mut file) => { let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) }, Err(e) => Err(MyError::Common(format!("Error reading file: {}", e))), } } ``` **使用上下文信息** 你可以使用上下文信息来提供有关文件路径或其他相关信息的详细信息。这种方法有助于你调试错误并定位文件缺失。

正文

rust 处理错误,不使用 try catch, 而是使用 Result<T, E>。

简单的处理rust错误

在各种关于rust错误处理的文档中,为了解释清楚其背后的机制,看着内容很多,不好理解。

比如我们写一个方法,读取文件内容:

fn read_file_to_string(file_path: String) -> Result<String, io::Error>{
    let mut file = File::open(file_path)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents)?;
    Ok(contents)
}

上面的代码,当文件不存在的时候,也可以很好的返回异常信息。

调用代码:

    let r = read_file_to_string(r"d:\1111.txt".to_string());
    match r {
        Ok(str) => println!("OK: {str}"),
        Err(e) => println!("Error: {e}"),
    };

如果文件不存在,会输出信息:

这个异常处理的过程不复杂,分为三步:

  1. 自定义的函数要返回Result<T,E>,

  2. 返回Result的函数时,后面加上问号,

  3. 在最上层,使用match处理结果。

但是这样是不够的,如果在一个大项目中,我们很难找到是哪个文件缺失了,rust不像c#那样,可以很容易的获取到出现问题的代码行数、类和方法名等。

最直观的方法是,在异常信息里,带上文件名。

自定义错误,带上文件名

rust自定义错误分为三步:

1)定义错误类型

2)实现Error特征(trait)

3)  实现Display特征

自定义错误的类型是enum, 和其他语言相比,这有点奇怪。 代码如下:

// 定义自定义错误类型
#[derive(Debug)]
pub enum MyError {
    FileOpenError(String),
    ParseError(String),
    Common(String),
}

// 实现Error特质
impl Error for MyError {}

// 实现Display特质以便打印错误信息
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MyError::FileOpenError(msg) => write!(f, "Failed to open file: {}", msg),
            MyError::ParseError(msg) => write!(f, "Parse error: {}", msg),
            MyError::Common(msg) => write!(f, "Other error: {}", msg),
        }
    }
}

这时,读取文件的函数代码要改成这样:

fn read_file_to_string(file_path: String) -> Result<String, MyError>{
    let r = File::open(file_path.clone());
    match r {
        Ok(mut file) => {
            let mut contents = String::new();
            let r2 = file.read_to_string(&mut contents);
            match r2 {
                Ok(size) => return Ok( contents),
                Err(e) => return Err(MyError::Common(format!("{e} 文件: {file_path}"))),
            } 
        },
        Err(e) => {
            return Err(MyError::FileOpenError(format!("{e} 文件: {file_path}")));
        },
    }
}

代码变得很啰嗦,好在能比较好的显示错误了:

自定义错误的三部曲,虽然有点长,但是这是项目的公共代码,还是可以忍受的。读取文件的代码,和 c#比起来,真的太罗嗦了。

简化通用异常处理

读取文件内容的函数,代码罗嗦的原因是,异常类型通过问号匹配到自定义的MyError很麻烦。

这里我们采用一种更通用的方式,来处理异常:

1) 重新定义异常类型,并且提供其他异常向自定义异常转换的方法

custom_error.rs:

use std::error::Error;
use std::fmt;
use std::fmt::Display;

// 自定义错误类型,包含文件路径信息
#[derive(Debug)]
pub struct MyError {
    msg: String,
    source: String,
}

// 为自定义错误类型实现Error trait
impl Error for MyError {}

// 实现Display trait,以便于打印错误信息
impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}: {}", self.source, self.msg)
    }
}

pub fn convert_error(msg:String, err: String) -> MyError {
    MyError {
        msg: msg ,
        source: err.to_string(),
    }
}

// 定义一个新的trait
pub trait MyErrorExtension<T> {
    fn ex_err(self, msg:&String)->  Result<T, MyError>;
}

// 为Result<T,E>类型实现MyExtension trait
impl<T,E:Display> MyErrorExtension<T> for Result<T,E> {
    fn ex_err(self, msg:&String) ->  Result<T, MyError> {
        match self {
            Ok(t) => Ok(t),
            Err(e) => Err(MyError{msg:msg.to_string(), source: e.to_string()}),
        }
    }
}

2) 定义带有通用异常处理能力的函数的示例:

fn read_file_to_string(file_path: String) -> Result<String, MyError>{
    let context_info = format!("文件路径: {file_path}");
    fs::metadata(&file_path).ex_err(&context_info)?;
    let mut file = File::open(&file_path).ex_err( &context_info)?;
    let mut contents = String::new();
    file.read_to_string(&mut contents).ex_err(&context_info)?;
    Ok(contents)
}

以打开文件的方法为例,原本的调用是:

let mut file = File::open(&file_path)?;

新的调用,后面附加了重要的上下文信息,并且把异常类型转换为MyError:

let mut file = File::open(&file_path).ex_err( &context_info)?;

通过扩展方法ex_err, 达到了我们的目的。

与Rust 错误处理相似的内容:

Rust 错误处理

rust 处理错误,不使用 try catch, 而是使用 Result。 简单的处理rust错误 在各种关于rust错误处理的文档中,为了解释清楚其背后的机制,看着内容很多,不好理解。 比如我们写一个方法,读取文件内容: fn read_file_to_string(file_path

文盘Rust -- 给程序加个日志

日志是应用程序的重要组成部分。无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录。在这篇文章中,我们结合log4rs聊聊rust 程序中如何使用日志。

文盘Rust -- 给程序加个日志

作者:贾世闻 日志是应用程序的重要组成部分。无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录。在这篇文章中,我们结合[log4rs](https://github.com/estk/log4rs)聊聊rust 程序中如何使用日志。 [log4rs](https://github.co

Rust 基础知识总结

一、所有权规则: Rust中的每一个值都有一个所有者(Owner); 值在任一时刻有且只有一个所有者; 当所有者(变量)离开作用域时,其对应的值被丢弃; 二、Move(转移) 原变量被赋值给其他变量以后,原变量不可用;编译时已知大小的类型除外;如整型; Copy trait; 如果一个值实现了Cop

Rust性能分析之测试及火焰图,附(lru,lfu,arc)测试

好的测试用例及性能测试是对一个库的稳定及优秀的重要标准,尽量的覆盖全的单元测试,能及早的发现bug,使程序更稳定。

Rust中的并发性:Sync 和 Send Traits

在并发的世界中,最常见的并发安全问题就是数据竞争,也就是两个线程同时对一个变量进行读写操作。但当你在 Safe Rust 中写出有数据竞争的代码时,编译器会直接拒绝编译。那么它是靠什么魔法做到的呢? 这就不得不谈 Send 和 Sync 这两个标记 trait 了,实现 Send 的类型可以在多线程

[转帖]Rust在windows下安装以后cargo build Error: linker `link.exe` not found

D:\rust\runoob-greeting\greeting>cargo build error: linker `link.exe` not found | = note: 系统找不到指定的文件。 (os error 2) note: the msvc targets depend on th

[转帖]wiki Rust

Rust[编辑] 维基百科,自由的百科全书 跳到导航跳到搜索 此条目介绍的是由Mozilla主导开发的编程语言。关于“rust”在英文中的本意,请见“铁锈”。关于由Facepunch工作室所开发的一款游戏,请见“腐蚀 (游戏)”。 Rust 编程范型 编译语言、并发计算、函数式、指令式、面向对象、结

文盘Rust -- 把程序作为守护进程启动

当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服。今天我们就来聊聊这个事儿。 最早大家部署应用的通常操作是 “nohup xxxx &”,别说像weblogic 或者其他java 容器有启动脚本,里面其实也差不多;很喜欢 ngi

文盘Rust -- r2d2 实现redis连接池

作者:贾世闻 我们在开发应用后端系统的时候经常要和各种数据库、缓存等资源打交道。这一期,我们聊聊如何访问redis 并将资源池化。 在一个应用后端程序访问redis主要要做的工作有两个,单例和池化。 在后端应用集成redis,我们主要用到以下几个crate:​ ​once_cell​​​、​ ​re