跳转至

Error Handling

本节介绍Rust的异常处理机制。

编译器错误

Rust中的部分错误可以在编译时被检测出来,这些错误被称为编译时错误(compile-time error)。使用rustc --explain可以查看编译时错误的详细信息。

rustc --explain E0382
# A variable was used after its contents were moved elsewhere.
#
# Erroneous code example:
#
# struct MyStruct { s: u32 }
#
# fn main() {
#    let mut x = MyStruct { s: 5u32 };
#    let y = x;
#    x.s = 6;
#    println!("{}", x.s);
# }
# ...

异常(Panic)

Rust中的异常被称为panic。当程序发生panic时,Rust会打印出异常信息并退出程序。有多个宏可以用于触发异常。

// 以下的宏也可以接受类似println!的格式化字符串
panic!("This is a panic!");
unimplemented!(); // 用于标记未实现的代码
unreachable!(); // 用于标记不可能到达的分支

// 以下宏用于测试
assert!(false); // 用于测试表达式是否为true
assert_eq!(1, 2); // 用于测试两个表达式的值是否相等
assert_ne!(1, 1); // 用于测试两个表达式的值是否不相等

// 在Debug模式或使用-C debug-assertions参数时,以下宏才会触发
debug_assert!(false);
debug_assert_eq!(1, 2);
debug_assert_ne!(1, 1);

异常处理

Rust中内置了两个类型Option<T>Result<T, E>用于异常处理,其定义为

enum Option<T> {
    None,
    Some(T),
}
enum Result<T, E> {
    Ok(T),
    Err(E),
}

以下,均以T代表Option::SomeResult::Ok的类型,E代表Result::Err的类型。

  • Option类型用于表示一个可能不存在的值,常用于函数参数、结构体字段或函数返回值,使用match语句可以匹配Option类型取值的不同情况。

    fn main() {
        let x: Option<i32> = Some(5);
        match x {
            Some(i) => println!("{}", i),
            None => println!("None"),
        }
    }
    
    1. 使用is_someis_none方法可以快速判断Option类型是否为SomeNone
    2. 使用unwrap方法可以将Option类型转换为T类型,如果Option类型为None,则会触发异常。
  • Result类型用于表示一个可能出错的值,常用于函数返回值。Ok表示函数执行成功,Err表示函数执行失败。

    fn main() {
        let x: Result<i32, &str> = Ok(5);
        match x {
            Ok(i) => println!("{}", i),
            Err(e) => println!("{}", e),
        }
    }
    
    1. 使用is_okis_err方法可以快速判断Result类型是否为OkErr
    2. 使用okerr方法可以将Result<T, E>类型转换为Option<T>类型。如果调用的方法和Result类型的取值相同,则返回Some(T),否则返回None

      fn main() {
          let x: Result<i32, &str> = Ok(5);
          let y: Option<i32> = x.ok();
          println!("{:?}", y); // Some(5)
          let x: Result<i32, &str> = Err("error");
          let y: Option<i32> = x.ok();
          println!("{:?}", y); // None
      }
      
    3. 使用unwrap方法或expect方法可以将Result类型转换为T类型,如果Result类型为Err,则会触发异常,输出的异常信息对应于Err中的值或expect方法的参数。

      fn main() {
          let x: Result<i32, &str> = Ok(5);
          let y: i32 = x.unwrap();
          println!("{}", y); // 5
          let x: Result<i32, &str> = Err("error");
          let y: i32 = x.unwrap(); // thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "error"', src/main.rs:2:26
          println!("{}", y);
      }
      

unwrap方法有如下变种:

  • expect方法,接受一个字符串作为参数,用于指定跳出异常时的提示信息。
  • (仅Result类型)unwrap_err方法和expect_err方法,与unwrap相反,当Result类型为Ok时,会触发异常,否则返回Err中的值。
  • unwrap_or方法,接受一个T类型的参数,在unwrap方法会触发异常的情况下,返回该参数。
  • unwrap_or_default方法,在unwrap方法会触发异常的情况下,返回该默认值。该方法要求T类型实现了Default特性。

    struct MyStruct {
        s: i32
    }
    
    fn main() {
        let a: Option<MyStruct> = None;
        println!("{}", a.unwrap_or_default().s);
        // error[E0277]: the trait bound `MyStruct: Default` is not satisfied
    }
    
  • unwrap_or_else方法,接受一个函数作为参数,在unwrap方法会触发异常的情况下,返回函数的返回值。其中,Result类型的闭包参数为E类型,Option类型的闭包参数为()类型,函数的返回值需要和SomeOk的类型相同。

    fn increment(x: i32) -> i32 {
        x + 1
    }
    
    fn main() {
        let a: Result<i32, i32> = Err(2);
        println!("{}", a.unwrap_or_else(increment));
        // 3
    }
    

运算

Rust可以在不取出OptionResult类型的值的情况下,对其进行运算。对于以下方法,记A为调用方法的对象,B为方法的参数,即A.method(B)

OptionResult的真值

不妨将OptionResult类型的SomeOk视为,其中的值T称为真值NoneErr视为Err的值E视为假值

  1. 逻辑运算A.or(B)A.and(B)方法,接受一个和自身类型相同的参数。
    • 对于or(),如果A为真,则返回A的真值,否则返回B值。
    • 对于and(),如果A为假,则返回A的假值,否则返回B
  2. A.or_else()A.or(B)的变种,接受一个函数作为参数。如果为真,则返回A的真值,否则返回函数的返回值。
  3. A.and_then()A.and(B)的变种,接受一个函数作为参数。如果为假,则返回A的假值,否则返回函数的返回值。
  4. (仅Option类型)A.filter(),接受一个函数fn (T) -> bool作为参数。如果A为真,且以其真值作为参数后调用函数返回真,则返回A的真值,否则返回None
  5. A.map(),接受一个函数fn (T) -> U作为参数。如果A为真,则返回A的真值作为参数后调用函数的返回值,否则返回A的假值。注意返回值的类型为Option<U>Result<U, E>
    • (仅Result类型)A.map_err(),接受一个函数fn (E) -> F作为参数。如果A为假,则返回A的假值作为参数后调用函数的返回值,否则返回A的真值。
    • (仅Option类型)A.map_or(),接受一个U类型的默认值和一个函数fn (T) -> U作为参数。如果A为真,则返回A的真值作为参数后调用函数的返回值,否则返回默认值。
    • A.map_or_else(),接受一个函数fn () -> U和一个函数fn (T) -> U作为参数。如果A为真,则返回A的真值作为参数后调用第二个函数的返回值,否则返回第一个函数的返回值。
  6. (仅Option类型)A.ok_or()用于将Option类型转换为Result类型,接受一个E类型的参数。如果A为真,则返回A的真值,否则返回参数作为Err的值。
    • A.ok_or_else()A.ok_or()的变种,接受一个函数作为参数。如果A为真,则返回A的真值,否则返回函数的返回值。
  7. A.as_ref()A.as_mut(),用于将Option<T>Result<T, E>类型转换其对应的引用。其中A.as_ref()返回不可变引用,即Option<&T>Result<&T, &E>A.as_mut()返回可变引用,即Option<&mut T>Result<&mut T, &mut E>

异常传递

Rust中,有两种方式可以向上返回异常。

  • ?运算符,用于将OptionResult类型转换为T类型,如果Result类型为Err或,则会直接返回Err中的值。

    fn fn_with_error<'a>() -> Result<i32, &'a str> {
        Err("Deterministic error")
    }
    fn use_fn<'a>() -> Result<i32, &'a str> {
        let x = fn_with_error()?; // Directly return Err("Deterministic error")
        Ok(x) // unreachable
    }
    fn main() {
        let x: &str = use_fn().unwrap_err();
        println!("{}", x); // Deterministic error
    }
    
  • try!宏,与?的效果等同,使用方式为try!(expression)。目前已经被弃用,使用?代替。

    fn fn_with_error<'a>() -> Result<i32, &'a str> {
        Err("Deterministic error")
    }
    fn use_fn<'a>() -> Result<i32, &'a str> {
        let x = try!(fn_with_error()); // error: use of deprecated `try` macro
        Ok(x) // unreachable
    }
    fn main() {
        let x: &str = use_fn().unwrap_err();
        println!("{}", x); // Deterministic error
    }
    

主函数

Rust的main函数只能取()Result<(), E>类型,其中Estd::error::Error的实现类型。如果main函数返回Result类型,则会将Err中的值打印到stderr并退出程序。

use std::fs::File;

fn main() -> std::io::Result<()> {
    let _ = File::open("not-existing-file.txt")?; // Result::Err

    Ok(()) // Default return value
    // Must with this line, otherwise return () which is incompatible
}

如果main函数的返回值类型不是()Result,则无法通过编译。

fn main() -> i32 {
    0
}
// error[E0277]: `main` has invalid return type `i32`

自定义异常

在Rust中可以自定义异常类型。异常类型需要实现std::error::Error特性,该特性定义了以下方法:

  • fn source(&self) -> Option<&(dyn Error + 'static)>,用于返回引起异常的原因(可选)。
  • fn Debug::fmt(&self, f: &mut Formatter<'_>) -> Result,用于格式化异常信息(用于调试)。
  • fn Display::fmt(&self, f: &mut Formatter<'_>) -> Result,用于格式化异常信息(用于输出)。
 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
struct MyError {
    code: i32,
    message: String
}

impl std::error::Error for MyError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
}

impl std::fmt::Debug for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f, "MyError {{ code: {}, message: {} }} at file {} line {}",
            self.code, self.message, file!(), line!()
            // file!() and line!() are macros to get the current file and line
        )
    }
}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "MyError {{ code: {}, message: {} }}", self.code, self.message)
    }
}

// Use the error

fn raise_error() -> Result<(), MyError> {
    Err(MyError { code: 1, message: "Error message".to_string() })
}

fn main() -> Result<(), MyError> {
    let _ = raise_error()?;
    // MyError { code: 1, message: Error message } at file src/main.rs line 16
    Ok(())
}

异常转换

由于Rust中的异常类型是静态的,因此在异常传递过程中,可能会遇到异常类型不匹配的情况。此时,可以使用From特性将异常类型转换为另一种异常类型。

以下以std::io::Error为例。

impl std::convert::From<std::io::Error> for MyError {
    fn from(error: std::io::Error) -> Self {
        MyError { code: 2, message: error.to_string() }
    }
}

此后,无需显式调用From特性的方法,即可将std::io::Error类型转换为MyError类型。

评论