rust 笔记 高级错误处理(二)

简介: rust 笔记 高级错误处理

rust 笔记 高级错误处理(一)https://developer.aliyun.com/article/1392099


上面的例子很简单,我们定义了一个错误类型,当为它派生了 Debug 特征,同时手动实现了 Display 特征后,该错误类型就可以作为 Err来使用了。

事实上,实现 Debug 和 Display 特征并不是作为 Err 使用的必要条件,大家可以把这两个特征实现和相应使用去除,然后看看代码会否报错。既然如此,我们为何要为自定义类型实现这两个特征呢?原因有二:

错误得打印输出后,才能有实际用处,而打印输出就需要实现这两个特征

可以将自定义错误转换成 Box 特征对象,在后面的归一化不同错误类型部分.

现在再来定义一个具有错误码和信息的错误:

use std::fmt;
struct AppError {
    code: usize,
    message: String,
}
// 根据错误码显示不同的错误信息
impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let err_msg = match self.code {
            404 => "Sorry, Can not find the Page!",
            _ => "Sorry, something is wrong! Please Try Again!",
        };
// 向一个缓冲区里写格式化的数据。
// write!将格式化的内容写入到指定的输出流中,例如write!(std::io::stdout(), "hello {}!", "world")将在标准输出流中打印出hello world!。
// print!也是对输出内容进行格式化,但是输出的目标是标准输出流
        write!(f, "{}", err_msg)
    }
}
impl fmt::Debug for AppError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(
            f,
            "AppError {{ code: {}, message: {} }}",
            self.code, self.message
        )
    }
}
// 测试错误方法
fn produce_error() -> Result<(), AppError> {
    Err(AppError {
        code: 404,
        message: String::from("Page not found"),
    })
}
fn main() {
    match produce_error() {
        Err(e) => eprintln!("{}", e), // 抱歉,未找到指定的页面!
        _ => println!("No error"),
    }
    eprintln!("{:?}", produce_error()); // Err(AppError { code: 404, message: Page not found })
    eprintln!("{:#?}", produce_error());
    // Err(
    //     AppError { code: 404, message: Page not found }
    // )
}

在本例中,我们除了增加了错误码和消息外,还手动实现了 Debug 特征,原因在于,我们希望能自定义 Debug 的输出内容,而不是使用派生后系统提供的默认输出形式。

错误转换 From 特征

标准库、三方库、本地库,各有各的精彩,各也有各的错误。那么问题就来了,我们该如何将其它的错误类型转换成自定义的错误类型?总不能神鬼牛魔,同台共舞吧。

好在 Rust 为我们提供了 std::convert::From 特征:

pub trait From<T>: Sized {
  fn from(_: T) -> Self;
}

大家都使用过 String::from 函数吧?它可以通过 &str 来创建一个 String,其实该函数就是 From 特征提供的

下面一起来看看如何为自定义类型实现 From 特征:

use std::fs::File;
use std::io;
#[derive(Debug)]
struct AppError {
    kind: String,    // 错误类型
    message: String, // 错误信息
}
// 为 AppError 实现 std::convert::From 特征,由于 From 包含在 std::prelude 中,因此可以直接简化引入。
// 实现 From<io::Error> 意味着我们可以将 io::Error 错误转换成自定义的 AppError 错误
impl From<io::Error> for AppError {
    fn from(error: io::Error) -> Self {
        AppError {
            kind: String::from("io"),
            message: error.to_string(),
        }
    }
}
fn main() -> Result<(), AppError> {
    let _file = File::open("nonexistent_file.txt")?;
    Ok(())
}
Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }

上面的代码中除了实现 From 外,还有一点特别重要,那就是 ? 可以将错误进行隐式的强制转换:File::open 返回的是 std::io::Error, 我们并没有进行任何显式的转换,它就能自动变成 AppError ,这就是 ? 的强大之处!当然实现隐式转换的前提是实现了对应的From;

再来看看多个不同的错误转换成 AppError 的实现:

use std::fs::File;
use std::io::{self, Read};
use std::num;
#[derive(Debug)]
struct AppError {
    kind: String,
    message: String,
}
impl From<io::Error> for AppError {
    fn from(error: io::Error) -> Self {
        AppError {
            kind: String::from("io"),
            message: error.to_string(),
        }
    }
}
impl From<num::ParseIntError> for AppError {
    fn from(error: num::ParseIntError) -> Self {
        AppError {
            kind: String::from("parse"),
            message: error.to_string(),
        }
    }
}
fn main() -> Result<(), AppError> {
    let mut file = File::open("hello_world.txt")?;
    let mut content = String::new();
    file.read_to_string(&mut content)?;
    let _number: usize;
    _number = content.parse()?;
    Ok(())
}
// 01. 若 hello_world.txt 文件不存在
Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }
// 02. 若用户没有相关的权限访问 hello_world.txt
Error: AppError { kind: "io", message: "Permission denied (os error 13)" }
// 03. 若 hello_world.txt 包含有非数字的内容,例如 Hello, world!
Error: AppError { kind: "parse", message: "invalid digit found in string" }

归一化不同的错误类型

在实际项目中,我们往往会为不同的错误定义不同的类型,这样做非常好,但是如果你要在一个函数中返回不同的错误呢?例如:

use std::fs::read_to_string;
fn main() -> Result<(), std::io::Error> {
  let html = render()?;
  println!("{}", html);
  Ok(())
}
fn render() -> Result<String, std::io::Error> {
  let file = std::env::var("MARKDOWN")?;
  let source = read_to_string(file)?;
  Ok(source)
}

上面的代码会报错,原因在于 render 函数中的两个 ? 返回的实际上是不同的错误:env::var() 返回的是 std::env::VarError,而 read_to_string 返回的是 std::io::Error。

为了满足 render 函数的签名,我们就需要将 env::VarError 和 io::Error 归一化为同一种错误类型。要实现这个目的有三种方式:

  • 使用特征对象 Box
  • 自定义错误类型
  • 使用 thiserror
    下面依次来看看相关的解决方式。
Box
use std::fs::read_to_string;
use std::error::Error;
fn main() -> Result<(), Box<dyn Error>> {
  let html = render()?;
  println!("{}", html);
  Ok(())
}
fn render() -> Result<String, Box<dyn Error>> {
  let file = std::env::var("MARKDOWN")?;
  let source = read_to_string(file)?;
  Ok(source)
}

这个方法很简单,在绝大多数场景中,性能也非常够用,但是有一个问题:Result 实际上不会限制错误的类型,也就是一个类型就算不实现 Error 特征,它依然可以在 Result 中作为 E 来使用,此时这种特征对象的解决方案就无能为力了。

自定义错误类型

与特征对象相比,自定义错误类型麻烦归麻烦,但是它非常灵活,因此也不具有上面的类似限制:

use std::fs::read_to_string;
fn main() -> Result<(), MyError> {
  let html = render()?;
  println!("{}", html);
  Ok(())
}
fn render() -> Result<String, MyError> {
  let file = std::env::var("MARKDOWN")?;
  let source = read_to_string(file)?;
  Ok(source)
}
#[derive(Debug)]
enum MyError {
  EnvironmentVariableNotFound,
  IOError(std::io::Error),
}
impl From<std::env::VarError> for MyError {
  fn from(_: std::env::VarError) -> Self {
    Self::EnvironmentVariableNotFound
  }
}
impl From<std::io::Error> for MyError {
  fn from(value: std::io::Error) -> Self {
    Self::IOError(value)
  }
}
impl std::error::Error for MyError {}
impl std::fmt::Display for MyError {
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
      MyError::EnvironmentVariableNotFound => write!(f, "Environment variable not found"),
      MyError::IOError(err) => write!(f, "IO Error: {}", err.to_string()),
    }
  }
}

上面代码中有一行值得注意:impl std::error::Error for MyError {} ,只有为自定义错误类型实现 Error 特征后,才能转换成相应的特征对象。

简化错误处理

对于开发者而言,错误处理是代码中打交道最多的部分之一,因此选择一把趁手的武器也很重要,它可以帮助我们节省大量的时间和精力,好钢应该用在代码逻辑而不是冗长的错误处理上。

thiserror

thiserror可以帮助我们简化上面的第二种解决方案:

use std::fs::read_to_string;
fn main() -> Result<(), MyError> {
  let html = render()?;
  println!("{}", html);
  Ok(())
}
fn render() -> Result<String, MyError> {
  let file = std::env::var("MARKDOWN")?;
  let source = read_to_string(file)?;
  Ok(source)
}
#[derive(thiserror::Error, Debug)]
enum MyError {
  #[error("Environment variable not found")]
  EnvironmentVariableNotFound(#[from] std::env::VarError),
  #[error(transparent)]
  IOError(#[from] std::io::Error),
}
anyhow

anyhow 和 thiserror 是同一个作者开发的,这里是作者关于 anyhow 和 thiserror 的原话:

如果你想要设计自己的错误类型,同时给调用者提供具体的信息时,就使用 thiserror,例如当你在开发一个三方库代码时。如果你只想要简单,就使用 anyhow,例如在自己的应用服务中。

use std::fs::read_to_string;
use anyhow::Result;
fn main() -> Result<()> {
    let html = render()?;
    println!("{}", html);
    Ok(())
}
fn render() -> Result<String> {
    let file = std::env::var("MARKDOWN")?;
    let source = read_to_string(file)?;
    Ok(source)
}

关于如何选用 thiserror 和 anyhow 只需要遵循一个原则即可:是否关注自定义错误消息,关注则使用 thiserror(常见业务代码),否则使用 anyhow(编写第三方库代码).

相关文章
|
7月前
|
Rust 安全 网络协议
Rust 笔记:Rust 语言中的枚举 与 模式匹配
Rust 笔记:Rust 语言中的枚举 与 模式匹配
90 0
|
3月前
|
Rust 安全
30天拿下Rust之错误处理
30天拿下Rust之错误处理
43 0
|
7月前
|
存储 Rust 安全
Rust 笔记:Rust 语言中的 所有权 与 生命周期
Rust 笔记:Rust 语言中的 所有权 与 生命周期
199 0
|
7月前
|
Rust 自然语言处理 算法
【Rust 中的错误处理:掌握 Option、Result、expect、unwrap 和 ? 运算符】Error Handling in Rust
【Rust 中的错误处理:掌握 Option、Result、expect、unwrap 和 ? 运算符】Error Handling in Rust
269 0
|
7月前
|
数据采集 存储 Rust
Rust高级爬虫:如何利用Rust抓取精美图片
Rust高级爬虫:如何利用Rust抓取精美图片
|
7月前
|
Rust 安全 开发者
Rust中的错误处理策略:Result类型与Panic
Rust语言以其强大的内存安全和并发编程能力而著称。在Rust中,错误处理是一个核心概念,其独特的处理方式体现在Result类型和Panic机制上。本文将深入探讨这两种错误处理策略在Rust中的应用,以及它们如何帮助开发者构建更加健壮和安全的程序。
|
7月前
|
存储 缓存 Rust
Rust 笔记:Rust 语言中哈希结构(哈希映射,HashMap)、集合(哈希集,HashSet)及其使用
Rust 笔记:Rust 语言中哈希结构(哈希映射,HashMap)、集合(哈希集,HashSet)及其使用
1030 0
|
7月前
|
存储 Rust 安全
Rust 笔记:Rust 语言中的 结构体 与面向对象编程的实现
Rust 笔记:Rust 语言中的 结构体 与面向对象编程的实现
111 0
|
7月前
|
存储 Rust 索引
Rust 笔记:Rust 语言中的运算符
Rust 笔记:Rust 语言中的运算符
242 0
|
7月前
|
存储 设计模式 人工智能
Rust 笔记:有限状态机原理/状态模式 及其 在Rust 编程中的应用
Rust 笔记:有限状态机原理/状态模式 及其 在Rust 编程中的应用
430 0