Rust笔记 - 错误处理

229 阅读3分钟

错误处理是程序的重要组成部分,一个程序的错误处理好坏,关系到程序是否健壮。Rust以安全高效著称,那Rust的错误处理是怎样的呢?Rust错误处理关键在:避免隐藏错误,暴露错误,错误要处理。不得不提Result<T, E> 类型,普通的错误处理都围绕这该类型展开。Rust还包含panic错误处理,panic会造成运行时恐慌,默认后果是程序异常退出。那什么时候用panic呢?

  • 示例代码,用panic能简化错误处理代码。
  • 确定不可能错误的Result<T, E>变量。 如: let a = Ok(1); let b = a.unwrap();unwarp的默认行为是panic,使用panic处理错误能简化错误处理。
  • 出现不能接受的错误,直接用panic退出程序,打印调用栈,比蹩脚的错误恢复处理要高明得多。

下面按照惯例看一个例子:

fn main() {
    panic!("expect panic");
}

下面将讨论更一般的错误处理机制:Result<T, E>。先看一下它的定义:

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

它的定义很简单,仅包含成功与失败两种可能。在错误处理机制中,我们最关心的就是操作是否成功;如果失败了再具体看原因。但很多时候我们会忽略错误处理,天真的认为不会失败。事实告诉我们在计算机的世界里可能失败的,一定会失败。而Rust为了避免错误被忽略掉,特地给Result加了#[must_use]注解,编译器会在编译期检查Result是否被处理了,用心良苦啊!

  • match处理错误:

    match get_result() {
        Ok(ok) => {
            display_ok(ok);
        }
        Err(err) => {
            display_err(err);
        }
    }
    

    上面是Rust经典的match处理模式。

  • ?传播错误:

    fn propagate_result(flag: bool) -> Result<(), &'static str> {
        get_result(flag)?;
        Ok(())
    }
    
    fn get_result(flag: bool) -> Result<(), &'static str> {
        if flag {
            return Ok(());
        } else {
            return Err("expect error");
        }
    }
    
    fn main() {
        println!("{:?}", propagate_result(false)); // 输出:Err("expect error")
    }
    

    上面使用?操作符来处理错误。当我们不希望处理繁琐的错误分支时,使用?来简化错误处理,遇到Err时直接返回Err

  • 忽略错误:

    fn get_result(flag: bool) -> Result<(), &'static str> {
        if flag {
            return Ok(());
        } else {
            return Err("expect error");
        }
    }
    
    fn main() {
       let _ = get_result(false);
    }
    

    有时候我们不想处理错误,可以向上面这样丢弃错误。

  • 自定义错误类型和返回不同错误类型:

    #[derive(Debug, Clone)]
    struct NumError {
        data: i32,
    }
    
    impl std::fmt::Display for NumError {
        fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
            write!(f, "NumError:(data: {})", self.data)
        }
    }
    
    impl std::error::Error for NumError {
        fn description(&self) -> &str {
            "except NumError"
        }
    }
    
    fn get_result(flag: i32) -> Result<(), Box<dyn std::error::Error>> {
        if flag < 0 {
            return Err(Box::new(NumError{data: 1}));
        } else if flag > 0 {
            return Err(Box::from(std::io::Error::new(
                std::io::ErrorKind::Other,
                "flag great than 0",
            )));
        } else {
            return Ok(());
        }
    }
    
    fn main() {
        println!("{:?}", get_result(1)); 
        println!("{:?}", get_result(-1));
        println!("{:?}", get_result(0));
    }
    

    输出:

    Err(Custom { kind: Other, error: "flag great than 0" })
    Err(NumError { data: 1 })
    Ok(())
    

    上面的例子通过实现std::error::Errorstd::fmt::Display两个trait来实现自定义错误类型。通过Box<dyn std::error::Error>来接收不同的错误类型,实现返回不同的错误类型。