Movatterモバイル変換


[0]ホーム

URL:


codecamp

错误处理

错误处理

不管是人是鼠,即使最如意的安排设计,结局也往往会出其不意。《致老鼠》 罗伯特·彭斯

有时候,事情会出乎意料的发生错误。重要的是要提前想好应对错误的方法。Rust 有丰富的支持错误处理方法来应对可能(老实说:将会)发生在您的程序中的错误。

主要有两种类型的错误可能发生在你的程序中:故障和异常。让我们谈谈两者之间的区别,然后讨论如何处理它们。接着,将讨论如何将故障升级为异常。

故障 VS 异常

Rust 使用两个术语来区分两种形式的错误:故障和异常。故障是可以用某种方式中恢复的错误。异常是一种不能恢复的错误。

我们说的 ”恢复“ 是什么意思?嗯,在大多数情况下,指的是预计一个错误的可能性。例如,考虑 parse 函数:

"5".parse();

这个方法将一个字符串转换成另一种类型。但因为它是一个字符串,你不能确保转换工作正常执行。例如,执行如下的转换会得到什么?

"hello5world".parse();

这是行不通的。所以我们知道,这个函数只会对一些特定的输入才能正常工作。这是预期行为。我们称这种错误为故障。

另一方面,有时,有意想不到的错误,或者我们不能恢复它。一个典型的例子是一个断言:

assert!(x == 5);

我们使用 assert! 说明参数是正确的。如果这不是正确的,那么这个断言就是错误的。错误的话,我们不就能继续在当前状态往下执行了。另一个例子是使用 unreachable!() 宏:

enum Event {    NewRelease;}fn probability(_: &Event) -> f64 {    // real implementation would be more complex, of course    0.95}fn descriptive_probability(event: Event) -> &'static str {    match probability(&event) {        1.00 => "certain",        0.00 => "impossible",        0.00 ... 0.25 => "very unlikely",        0.25 ... 0.50 => "unlikely",        0.50 ... 0.75 => "likely",        0.75 ... 1.00 => "very likely",    }}fn main() {    std::io::println(descriptive_probability(NewRelease));}

它将会输出如下的错误:

error: non-exhaustive patterns: `_` not covered [E0004]

尽管我们已经涵盖所有我们知道的可能情况情况,但是 Rust 不清楚。Rust 不知道概率是 0.0 和 1.0 之间。所以我们添加另一个例子:

use Event::NewRelease;enum Event {    NewRelease,}fn probability(_: &Event) -> f64 {    // real implementation would be more complex, of course    0.95}fn descriptive_probability(event: Event) -> &'static str {    match probability(&event) {        1.00 => "certain",        0.00 => "impossible",        0.00 ... 0.25 => "very unlikely",        0.25 ... 0.50 => "unlikely",        0.50 ... 0.75 => "likely",        0.75 ... 1.00 => "very likely",        _ => unreachable!()    }}fn main() {    println!("{}", descriptive_probability(NewRelease));}

我们不应该得到 _ 情况,所以我们使用 unreachable!() 宏来说明这个。unreachable!() 比结果给出了不同于 Result 类型的错误。Rust 称这些类型的错误为异常。

利用 Option 和 Result 处理错误

表明一个函数可能会失败的最简单方法是使用 option< T >类型。例如,字符串 find 方法试图找到字符串的一个模式串,并返回 Option:

let s = "foo";assert_eq!(s.find('f'), Some(0));assert_eq!(s.find('z'), None);

对这些简单的情况下是可以的,但是在故障的情况下并不会给我们提供很多的信息。如果我们想知道为什么函数发生了故障,怎么办?为此,我们可以使用 Result<T, E>类型。它看起来像这样:

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

这个枚举类型由 Rust 本身提供,所以你不需要在你的代码中定义就可以使用它。Ok(T) 变量代表着成功执行,Err(E) 变量代表着执行失败。推荐在大多数情况下返回一个 Result而不是一个 Option 变量。

如下是一个使用 Result 的例子:

#[derive(Debug)]enum Version { Version1, Version2 }#[derive(Debug)]enum ParseError { InvalidHeaderLength, InvalidVersion }fn parse_version(header: &[u8]) -> Result<Version, ParseError> {    if header.len() < 1 {        return Err(ParseError::InvalidHeaderLength);    }    match header[0] {        1 => Ok(Version::Version1),        2 => Ok(Version::Version2),        _ => Err(ParseError::InvalidVersion)    }}let version = parse_version(&[1, 2, 3, 4]);match version {    Ok(v) => {        println!("working with version: {:?}", v);    }    Err(e) => {        println!("error parsing header: {:?}", e);    }}

这个函数使用枚举类型变量 ParseError 列举各种可能发生的错误。

调试特点就是让我们使用 {:?} 格式来打印该枚举变量的值。

遇到 panic!类型的不可恢复错误

遇到为意料的和不可恢复的的错误时,宏 panic! 会引起异常。这将崩溃当前线程,并给出一个错误:

panic!("boom");

当你运行时会输出:

thred '<main>' panicked at 'boom', hello.rs:2

因为这些类型的情况相对较少见,很少使用恐慌。

升级故障为异常

在某些情况下,即使一个函数可能发生故障,我们仍想要把它当作一个异常对待。例如,io::stdin().read_line(&mut buff) 函数在读取某一行时出现错误会返回Result< usize > 变量。这使我们能够处理它并可能从错误中恢复。

如果我们不想处理这个错误,而宁愿只是中止程序,那么我们可以使用 unwrap() 方法:

io::stdin().read_line(&mut buffer).unwrap();

如果 Result 变量值是 Err,unwrap() 方法将会产生调用 panic!,输出异常。这基本上是说“给我变量的值,如果出现错误,就让程序崩溃。“这相对于匹配错误并试图恢复的方式可靠性较低,但也大大缩短执行时间。有时,只是崩溃程序是合理的。

有另一种方式比 unwrap() 方法好一点:

let mut buffer = String::new();let input = io::stdin().read_line(&mut buffer)                       .ok()                       .expect("Failed to read line");

ok() 方法将 Result 转换成一个 Option,并 expect() 和 unwrap() 方法做的事是一样的,不同点在于它需要一个参数,用来输出提示信息。这个消息被传递到底层的 panic!,如果代码出现错误,它提供一个较好的错误消息展示方式。

使用 try!

当编写的代码调用很多的返回 Result 类型的函数时,错误处理就变得比较冗长。try! 宏利用堆栈对产生的错误进行引用从而隐藏具体的细节。

将如下的代码:

use std::fs::File;use std::io;use std::io::prelude::*;struct Info {    name: String,    age: i32,    rating: i32,}fn write_info(info: &Info) -> io::Result<()> {    let mut file = File::create("my_best_friends.txt").unwrap();    if let Err(e) = writeln!(&mut file, "name: {}", info.name) {        return Err(e)    }    if let Err(e) = writeln!(&mut file, "age: {}", info.age) {        return Err(e)    }    if let Err(e) = writeln!(&mut file, "rating: {}", info.rating) {        return Err(e)    }    return Ok(());}

替换成:

use std::fs::File;use std::io;use std::io::prelude::*;struct Info {    name: String,    age: i32,    rating: i32,}fn write_info(info: &Info) -> io::Result<()> {    let mut file = try!(File::create("my_best_friends.txt"));    try!(writeln!(&mut file, "name: {}", info.name));    try!(writeln!(&mut file, "age: {}", info.age));    try!(writeln!(&mut file, "rating: {}", info.rating));    return Ok(());}

用 try! 封装一个表达式,在成功执行时给 Result 赋值为 Ok,否则赋值为 Err,在这种情况下,在函数未执行完成之前就会返回 Err 值。

值得注意的是,你只能在返回 Result 的函数中使用 try!,这意味着您不能在 main() 中使用 try!,因为 main() 不返回任何值。

try! 利用From 来确定在错误的情况下返回的值。

并发性
外部函数接口
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录
关于
介绍

新手入门

学习 Rust

高效 Rust

语法和语义

Nightly Rust

词汇表
相关学术研究

关闭

MIP.setData({'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false},'pageFontSize' : getCookie('pageFontSize') || 20});MIP.watch('pageTheme', function(newValue){setCookie('pageTheme', JSON.stringify(newValue))});MIP.watch('pageFontSize', function(newValue){setCookie('pageFontSize', newValue)});function setCookie(name, value){var days = 1;var exp = new Date();exp.setTime(exp.getTime() + days*24*60*60*1000);document.cookie = name + '=' + value + ';expires=' + exp.toUTCString();}function getCookie(name){var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)');return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null;}
[8]ページ先頭

©2009-2025 Movatter.jp