• rust 笔记 高级错误处理


    错误处理

    组合器

    与组合器模式有所不同,在 Rust 中,组合器更多的是用于对返回结果的类型进行变换:例如使用 ok_or 将一个 Option 类型转换成 Result 类型。

    or() 和 and()

    跟布尔关系的与/或很像,这两个方法会对两个表达式做逻辑组合,最终返回 Option / Result。

    • or(),表达式按照顺序求值,若任何一个表达式的结果是 Some 或 Ok,则该值会立刻返回
    • and(),若两个表达式的结果都是 Some 或 Ok,则第二个表达式中的值被返回。若任何一个的结果是 None 或 Err ,则立刻返回。
      实际上,只要将布尔表达式的 true / false,替换成 Some / None 或 Ok / Err 就很好理解了。
    fn main() {
      let s1 = Some("some1");
      let s2 = Some("some2");
      let n: Option<&str> = None;
    
      let o1: Result<&str, &str> = Ok("ok1");
      let o2: Result<&str, &str> = Ok("ok2");
      let e1: Result<&str, &str> = Err("error1");
      let e2: Result<&str, &str> = Err("error2");
    
      assert_eq!(s1.or(s2), s1); // Some1 or Some2 = Some1
      assert_eq!(s1.or(n), s1);  // Some or None = Some
      assert_eq!(n.or(s1), s1);  // None or Some = Some
      assert_eq!(n.or(n), n);    // None1 or None2 = None2
    
      assert_eq!(o1.or(o2), o1); // Ok1 or Ok2 = Ok1
      assert_eq!(o1.or(e1), o1); // Ok or Err = Ok
      assert_eq!(e1.or(o1), o1); // Err or Ok = Ok
      assert_eq!(e1.or(e2), e2); // Err1 or Err2 = Err2
    
      assert_eq!(s1.and(s2), s2); // Some1 and Some2 = Some2
      assert_eq!(s1.and(n), n);   // Some and None = None
      assert_eq!(n.and(s1), n);   // None and Some = None
      assert_eq!(n.and(n), n);    // None1 and None2 = None1
    
      assert_eq!(o1.and(o2), o2); // Ok1 and Ok2 = Ok2
      assert_eq!(o1.and(e1), e1); // Ok and Err = Err
      assert_eq!(e1.and(o1), e1); // Err and Ok = Err
      assert_eq!(e1.and(e2), e1); // Err1 and Err2 = Err1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    除了 or 和 and 之外,Rust 还为我们提供了 xor ,但是它只能应用在 Option 上.

    or_else() 和 and_then()

    它们跟 or() 和 and() 类似,唯一的区别在于,它们的第二个表达式是一个闭包。

    fn main() {
        // or_else with Option
        let s1 = Some("some1");
        let s2 = Some("some2");
        let fn_some = || Some("some2"); // 类似于: let fn_some = || -> Option<&str> { Some("some2") };
    
        let n: Option<&str> = None;
        let fn_none = || None;
    
        assert_eq!(s1.or_else(fn_some), s1);  // Some1 or_else Some2 = Some1
        assert_eq!(s1.or_else(fn_none), s1);  // Some or_else None = Some
        assert_eq!(n.or_else(fn_some), s2);   // None or_else Some = Some
        assert_eq!(n.or_else(fn_none), None); // None1 or_else None2 = None2
    
        // or_else with Result
        let o1: Result<&str, &str> = Ok("ok1");
        let o2: Result<&str, &str> = Ok("ok2");
        let fn_ok = |_| Ok("ok2"); // 类似于: let fn_ok = |_| -> Result<&str, &str> { Ok("ok2") };
    
        let e1: Result<&str, &str> = Err("error1");
        let e2: Result<&str, &str> = Err("error2");
        let fn_err = |_| Err("error2");
    
        assert_eq!(o1.or_else(fn_ok), o1);  // Ok1 or_else Ok2 = Ok1
        assert_eq!(o1.or_else(fn_err), o1); // Ok or_else Err = Ok
        assert_eq!(e1.or_else(fn_ok), o2);  // Err or_else Ok = Ok
        assert_eq!(e1.or_else(fn_err), e2); // Err1 or_else Err2 = Err2
    }
    fn main() {
        // and_then with Option
        let s1 = Some("some1");
        let s2 = Some("some2");
        let fn_some = |_| Some("some2"); // 类似于: let fn_some = |_| -> Option<&str> { Some("some2") };
    
        let n: Option<&str> = None;
        let fn_none = |_| None;
    
        assert_eq!(s1.and_then(fn_some), s2); // Some1 and_then Some2 = Some2
        assert_eq!(s1.and_then(fn_none), n);  // Some and_then None = None
        assert_eq!(n.and_then(fn_some), n);   // None and_then Some = None
        assert_eq!(n.and_then(fn_none), n);   // None1 and_then None2 = None1
    
        // and_then with Result
        let o1: Result<&str, &str> = Ok("ok1");
        let o2: Result<&str, &str> = Ok("ok2");
        let fn_ok = |_| Ok("ok2"); // 类似于: let fn_ok = |_| -> Result<&str, &str> { Ok("ok2") };
    
        let e1: Result<&str, &str> = Err("error1");
        let e2: Result<&str, &str> = Err("error2");
        let fn_err = |_| Err("error2");
    
        assert_eq!(o1.and_then(fn_ok), o2);  // Ok1 and_then Ok2 = Ok2
        assert_eq!(o1.and_then(fn_err), e2); // Ok and_then Err = Err
        assert_eq!(e1.and_then(fn_ok), e1);  // Err and_then Ok = Err
        assert_eq!(e1.and_then(fn_err), e1); // Err1 and_then Err2 = Err1
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    filter

    filter 用于对 Option 进行过滤:

    fn main() {
        let s1 = Some(3);
        let s2 = Some(6);
        let n = None;
    
        let fn_is_even = |x: &i8| x % 2 == 0;
    
        assert_eq!(s1.filter(fn_is_even), n);  // Some(3) -> 3 is not even -> None
        assert_eq!(s2.filter(fn_is_even), s2); // Some(6) -> 6 is even -> Some(6)
        assert_eq!(n.filter(fn_is_even), n);   // None -> no value -> None
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    map() 和 map_err()

    map 可以将 Some 或 Ok 中的值映射为另一个同类型的值:

    fn main() {
        let s1 = Some("abcde");
        let s2 = Some(5);
    
        let n1: Option<&str> = None;
        let n2: Option = None;
    
        let o1: Result<&str, &str> = Ok("abcde");
        let o2: Result = Ok(5);
    
        let e1: Result<&str, &str> = Err("abcde");
        let e2: Result = Err("abcde");
    // 统计字符串中
        let fn_character_count = |s: &str| s.chars().count();
    
        assert_eq!(s1.map(fn_character_count), s2); // Some1 map = Some2
        assert_eq!(n1.map(fn_character_count), n2); // None1 map = None2
    
        assert_eq!(o1.map(fn_character_count), o2); // Ok1 map = Ok2
        assert_eq!(e1.map(fn_character_count), e2); // Err1 map = Err2
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    但是如果你想要将 Err 中的值进行改变,此时我们需要用 map_err:

    fn main() {
        let o1: Result<&str, &str> = Ok("abcde");
        let o2: Result<&str, isize> = Ok("abcde");
    
        let e1: Result<&str, &str> = Err("404");
        let e2: Result<&str, isize> = Err(404);
    
        let fn_character_count = |s: &str| -> isize { s.parse().unwrap() }; // 该函数返回一个 isize
    
        assert_eq!(o1.map_err(fn_character_count), o2); // Ok1 map = Ok2
        assert_eq!(e1.map_err(fn_character_count), e2); // Err1 map = Err2
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    map_or() 和 map_or_else()

    map_or 在 map 的基础上提供了一个默认值:

    fn main() {
        const V_DEFAULT: u32 = 1;
    
        let s: Result = Ok(10);
        let n: Option = None;
        let fn_closure = |v: u32| v + 2;
    
        assert_eq!(s.map_or(V_DEFAULT, fn_closure), 12);
        assert_eq!(n.map_or(V_DEFAULT, fn_closure), V_DEFAULT);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    如上所示,当处理 None 的时候,V_DEFAULT 作为默认值被直接返回。

    map_or_else 与 map_or 类似,但是它是通过一个闭包来提供默认值:

    fn main() {
        let s = Some(10);
        let n: Option = None;
    
        let fn_closure = |v: i8| v + 2;
        let fn_default = || 1;
    
        assert_eq!(s.map_or_else(fn_default, fn_closure), 12);
        assert_eq!(n.map_or_else(fn_default, fn_closure), 1);
    
        let o = Ok(10);
        let e = Err(5);
        let fn_default_for_result = |v: i8| v + 1; // 闭包可以对 Err 中的值进行处理,并返回一个新值
    
        assert_eq!(o.map_or_else(fn_default_for_result, fn_closure), 12);
        assert_eq!(e.map_or_else(fn_default_for_result, fn_closure), 6);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    ok_or() and ok_or_else()

    这两兄弟可以将 Option 类型转换为 Result 类型。其中 ok_or 接收一个默认的 Err 参数:

    fn main() {
        const ERR_DEFAULT: &str = "error message";
    
        let s = Some("abcde");
        let n: Option<&str> = None;
    
        let o: Result<&str, &str> = Ok("abcde");
        let e: Result<&str, &str> = Err(ERR_DEFAULT);
    
        assert_eq!(s.ok_or(ERR_DEFAULT), o); // Some(T) -> Ok(T)
        assert_eq!(n.ok_or(ERR_DEFAULT), e); // None -> Err(default)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    而 ok_or_else 接收一个闭包作为 Err 参数:

    fn main() {
        let s = Some("abcde");
        let n: Option<&str> = None;
        let fn_err_message = || "error message";
    
        let o: Result<&str, &str> = Ok("abcde");
        let e: Result<&str, &str> = Err("error message");
    
        assert_eq!(s.ok_or_else(fn_err_message), o); // Some(T) -> Ok(T)
        assert_eq!(n.ok_or_else(fn_err_message), e); // None -> Err(default)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    自定义错误类型

    虽然标准库定义了大量的错误类型,但是一个严谨的项目,光使用这些错误类型往往是不够的,例如我们可能会为暴露给用户的错误定义相应的类型。

    为了帮助我们更好的定义错误,Rust 在标准库中提供了一些可复用的特征,例如 std::error::Error 特征:

    use std::fmt::{Debug, Display};
    
    pub trait Error: Debug + Display {
        fn source(&self) -> Option<&(Error + 'static)> { ... }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当自定义类型实现该特征后,该类型就可以作为 Err 来使用。

    实际上,自定义错误类型只需要实现 Debug 和 Display 特征即可,source 方法是可选的,而 Debug 特征往往也无需手动实现,可以直接通过 derive 来派生

    最简单的错误

    use std::fmt;
    
    // AppError 是自定义错误类型,它可以是当前包中定义的任何类型,在这里为了简化,我们使用了单元结构体作为例子。
    // 为 AppError 自动派生 Debug 特征
    #[derive(Debug)]
    struct AppError;
    
    // 为 AppError 实现 std::fmt::Display 特征
    impl fmt::Display for AppError {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "An Error Occurred, Please Try Again!") // user-facing output
        }
    }
    
    // 一个示例函数用于产生 AppError 错误
    fn produce_error() -> Result<(), AppError> {
        Err(AppError)
    }
    
    fn main(){
        match produce_error() {
            Err(e) => eprintln!("{}", e),
            _ => println!("No error"),
        }
    
        eprintln!("{:?}", produce_error()); // Err({ file: src/main.rs, line: 17 })
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    上面的例子很简单,我们定义了一个错误类型,当为它派生了 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 }
        // )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

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

    错误转换 From 特征

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

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

    pub trait From: Sized {
      fn from(_: T) -> Self;
    }
    
    • 1
    • 2
    • 3

    大家都使用过 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 错误转换成自定义的 AppError 错误
    impl From 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(())
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    Error: AppError { kind: "io", message: "No such file or directory (os error 2)" }
    
    • 1

    上面的代码中除了实现 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 for AppError {
        fn from(error: io::Error) -> Self {
            AppError {
                kind: String::from("io"),
                message: error.to_string(),
            }
        }
    }
    
    impl From 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(())
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    // 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" }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    归一化不同的错误类型

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

    use std::fs::read_to_string;
    
    fn main() -> Result<(), std::io::Error> {
      let html = render()?;
      println!("{}", html);
      Ok(())
    }
    
    fn render() -> Result {
      let file = std::env::var("MARKDOWN")?;
      let source = read_to_string(file)?;
      Ok(source)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    上面的代码会报错,原因在于 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> {
      let html = render()?;
      println!("{}", html);
      Ok(())
    }
    
    fn render() -> Result> {
      let file = std::env::var("MARKDOWN")?;
      let source = read_to_string(file)?;
      Ok(source)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

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

    自定义错误类型

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

    use std::fs::read_to_string;
    
    fn main() -> Result<(), MyError> {
      let html = render()?;
      println!("{}", html);
      Ok(())
    }
    
    fn render() -> Result {
      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 for MyError {
      fn from(_: std::env::VarError) -> Self {
        Self::EnvironmentVariableNotFound
      }
    }
    
    impl From 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()),
        }
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    上面代码中有一行值得注意: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 {
      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),
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    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 {
        let file = std::env::var("MARKDOWN")?;
        let source = read_to_string(file)?;
        Ok(source)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

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

  • 相关阅读:
    Kotlin协程:flowOn与线程切换
    python的入门知识(下)
    tensorflow2 -------------LeNet--------------
    请体验一下falcon 180b 大语言模型的感觉
    【MRC转化为ARC Objective-C语言】
    论文阅读(单词记录)Investigating Attention Mechanism in 3D Point Cloud Object Detection
    【容器化】浅析容器化以及容器编排
    数据库学习之B-树
    重链剖分链加与极值
    开发手机软件需要学什么内容才可以呢
  • 原文地址:https://blog.csdn.net/studycodeday/article/details/134363341