Rust将谬误分为两大类:可复原谬误(recoverable)和不可复原谬误(unrecoverable)。 对于可复原的谬误,例如找不到文件谬误,能够正当地向用户报告问题,而后重试该操作。不可复原的谬误始终是谬误的症状,例如试图拜访数组开端以外的地位。

不可复原的谬误与panic!

有时,咱们的代码中会产生好事,而您对此无能为力。 在这些状况下,Rust会执行panic!宏命令。 当panic!宏执行后,程序将打印一条失败音讯,开展并清理堆栈,而后退出。最常见的状况是在检测到某种谬误并且程序员不分明如何解决该谬误时产生。

让咱们在一个简略的程序中尝试panic!

fn main() {    panic!("解体退出了");}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)    Finished dev [unoptimized + debuginfo] target(s) in 0.53s     Running `target\debug\cargo_learn.exe`thread 'main' panicked at '解体退出了', src\main.rs:2:5note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceerror: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

panic!宏命令与js中的throw Error属于同一性质,所以绝对于nodejs而言是同一个概念

应用panic!回溯
让咱们借助于一个例子来看看啥时候panin!宏命令会被调用,调用不是咱们的代码被动调用的是来自库的调用,当咱们的代码存在谬误的时候。

fn main() {    let v = vec![1, 2, 3];    v[99];}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)    Finished dev [unoptimized + debuginfo] target(s) in 0.55s     Running `target\debug\cargo_learn.exe`thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src\main.rs:4:5note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceerror: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

在这里,咱们尝试拜访向量的第100个元素(索引从零开始,因而它位于索引99),然而它只有3个元素。 在这种状况下,Rust会panic。应该应用[]返回一个元素,然而如果传递有效索引,则Rust不会在此处返回正确的元素。

可复原的谬误与Result

大多数谬误的重大水平还不足以要求程序齐全进行。 有时,当一个函数失败时,是出于能够轻松解释和响应的起因。 例如,如果尝试关上一个文件而该操作因为该文件不存在而失败,那么可能要创立该文件而不是终止该过程。例如:

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

T和E是泛型类型参数:T示意胜利状况下在Ok变量中将返回的值的类型,E示意在Err变体内的故障状况下将返回的谬误的类型。 因为Result具备这些通用类型参数,所以咱们能够应用Result类型和规范库在许多不同的状况下(咱们要返回的胜利值和谬误值可能不同)定义的函数。

让咱们调用一个返回Result值的函数,因为该函数可能会失败。尝试关上一个文件:

use std::fs::File;fn main() {    let f = File::open("hello.txt");    println!("{:?}", f)}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)    Finished dev [unoptimized + debuginfo] target(s) in 0.56s     Running `target\debug\cargo_learn.exe`Err(Os { code: 2, kind: NotFound, message: "零碎找不到指定的文件。" })

特地上例中的f是Result<File,Error>类型,如果指定其余类型将会报错,比方:

use std::fs::File;fn main() {    let f: u32 = File::open("hello.txt");}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)error[E0308]: mismatched types --> src\main.rs:4:18  |4 |     let f: u32 = File::open("hello.txt");  |            ---   ^^^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found enum `std::result::Result`  |            |  |            expected due to this  |  = note: expected type `u32`             found enum `std::result::Result<std::fs::File, std::io::Error>`error: aborting due to previous errorFor more information about this error, try `rustc --explain E0308`.error: could not compile `cargo_learn`.To learn more, run the command again with --verbose.

咱们还能够应用match表达式来自定义对于返回值执行不同的操作:

use std::fs::File;fn main() {    let f = File::open("hello.txt");    let f = match f {        Ok(file) => file,        Err(error) => panic!("关上文件失败: {:?}", error),    };    println!("{:?}", f)}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)    Finished dev [unoptimized + debuginfo] target(s) in 0.54s     Running `target\debug\cargo_learn.exe`thread 'main' panicked at '关上文件失败: Os { code: 2, kind: NotFound, message: "零碎找不到指定的文件。" }', src\main.rs:8:23note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceerror: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

匹配不同的谬误
上例中的代码会panic! 不论为什么File::open失败。 相同,咱们要针对不同的失败起因采取不同的操作:如果File::open因为文件不存在而失败,咱们想要创立文件并将句柄返回到新文件。 如果File::open因为其余任何起因失败(例如,因为咱们没有关上文件的权限),咱们依然心愿代码解体! 与上例雷同,增加一个外部match表达式:

use std::fs::File;use std::io::ErrorKind;fn main() {    let f = File::open("hello.txt");    let f = match f {        Ok(file) => file,        Err(error) => match error.kind() {            ErrorKind::NotFound => match File::create("hello.txt") {                Ok(fc) => fc,                Err(e) => panic!("创立文件失败: {:?}", e),            },            other_error => {                panic!("关上文件失败: {:?}", other_error)            }        },    };}

谬误panic的快捷方式:unwrapexpect

应用match成果很好,然而可能有点简短,而且不肯定总是能很好地传播用意。 Result <T,E>类型具备定义在其上的许多辅助办法来执行各种工作。其中一种办法称为unwrap,是一种快捷办法。如果Result值是Ok变量,则开展将在Ok外部返回值。 如果Result值是Err变体,unwrap将引起panic! 咱们的宏。 这是unwrap操作的示例:

use std::fs::File;fn main() {    let f = File::open("hello.txt").unwrap();}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)warning: unused variable: `f` --> src\main.rs:4:9  |4 |     let f = File::open("hello.txt").unwrap();  |         ^ help: if this is intentional, prefix it with an underscore: `_f`  |  = note: `#[warn(unused_variables)]` on by defaultwarning: 1 warning emitted    Finished dev [unoptimized + debuginfo] target(s) in 0.55s     Running `target\debug\cargo_learn.exe`thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "零碎找不到指定的文件。" }', src\main.rs:4:13note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceerror: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

另外一种办法:expect,与unwrap类似,都会导致调用panic!。应用expectunwrap相比能够很好的自定义错误信息。应用expect的办法如下:

use std::fs::File;fn main() {    let f = File::open("hello.txt").expect("关上文件hello.txt出错!");}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)warning: unused variable: `f` --> src\main.rs:4:9  |4 |     let f = File::open("hello.txt").expect("关上文件hello.txt出错!");  |         ^ help: if this is intentional, prefix it with an underscore: `_f`  |  = note: `#[warn(unused_variables)]` on by defaultwarning: 1 warning emitted    Finished dev [unoptimized + debuginfo] target(s) in 0.56s     Running `target\debug\cargo_learn.exe`thread 'main' panicked at '关上文件hello.txt出错!: Os { code: 2, kind: NotFound, message: "零碎找不到指定的文件。" }', src\main.rs:4:13note: run with `RUST_BACKTRACE=1` environment variable to display a backtraceerror: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 101)

谬误传递

当编写一个其实现调用可能会失败的函数时,能够将谬误返回给调用代码,以便它能够决定要做什么,而不是解决该函数中的谬误。这被称为谬误传递,并赋予调用代码更多的控制权,那里可能有更多的信息或逻辑来规定应如何处理错误,而不是代码上下文中可用的信息或逻辑。
比方:上面这个从文件中读取特定数据的函数。如果文件不存在或无奈读取,则此函数会将这些谬误返回给调用此函数的代码:

#![allow(unused_variables)]fn main() {    use std::fs::File;    use std::io;    use std::io::Read;    fn read_username_from_file() -> Result<String, io::Error> {        let f = File::open("hello.txt");        let mut f = match f {            Ok(file) => file,            Err(e) => return Err(e),        };        let mut s = String::new();        match f.read_to_string(&mut s) {            Ok(_) => Ok(s),            Err(e) => Err(e),        }    }    let test = read_username_from_file();    println!("{:?}", test);}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)    Finished dev [unoptimized + debuginfo] target(s) in 0.66s     Running `target\debug\cargo_learn.exe`Err(Os { code: 2, kind: NotFound, message: "零碎找不到指定的文件。" })

此函数能够用更短的形式编写,然而咱们将首先手动进行很多操作,以探讨错误处理;最初,咱们将展现较短的办法。咱们先来看一下函数的返回类型:Result <String, io::Error>。这意味着函数将返回类型Result <T,E>的值,其中通用参数T已用具体类型String填充,而通用类型E已已具体类型io::Error填充。如果此函数胜利执行而没有任何问题,则调用此函数的代码将收到一个蕴含字符串的Ok值,该字符串是该函数从文件中读取的用户名。如果此函数遇到任何问题,则调用此函数的代码将收到一个Err值,其中蕴含io::Error实例,该实例蕴含无关问题所在的更多信息。咱们抉择io::Error作为此函数的返回类型,因为它恰好是从咱们在该函数的主体中调用的两个操作(可能会失败)返回的谬误值的类型:File::open函数和read_to_string办法。

该函数的主体通过调用File::open函数开始。 而后,咱们解决返回的Result值,其后果相似于清单9-4中的匹配,而不仅仅是调用panic! 在Err的状况下,咱们会从此函数尽早返回,并将File::open的谬误值作为该函数的谬误值传递回调用代码。 如果File::open胜利,咱们将文件句柄存储在变量f中并持续。

而后,在变量s中创立一个新的String,并在f中的文件句柄上调用read_to_string办法,以将文件的内容读入s中。 read_to_string办法也返回后果,因为即便File::open胜利,它也可能失败。 因而,咱们须要另一个匹配项来解决该后果:如果read_to_string胜利,则阐明咱们的函数已胜利,并且咱们从文件中返回了用户名,该文件名已由Ok包裹。如果read_to_string失败,咱们将以与解决File::open的返回值的匹配中返回谬误值雷同的形式返回谬误值。然而,咱们不须要明确地说return,因为这是函数中的最初一个表达式。

而后,调用该代码的代码将解决获取蕴含用户名的Ok值或蕴含io::Error的Err值。 咱们不晓得调用代码将如何解决这些值。 如果调用代码取得Err值,则可能会呈现panic!并解体程序,应用默认用户名,或从文件以外的其余地位查找用户名。 咱们没有足够的信息来理解调用代码的理论作用,因而咱们向上流传所有胜利或错误信息,以使其可能正确处理。

传递谬误的捷径:? 操作

应用?操作实现与上例齐全一样的性能:

#![allow(unused_variables)]fn main() {    use std::fs::File;    use std::io;    use std::io::Read;    fn read_username_from_file() -> Result<String, io::Error> {        let mut f = File::open("hello.txt")?;        let mut s = String::new();        f.read_to_string(&mut s)?;        Ok(s)    }    let test = read_username_from_file();    println!("{:?}", test);}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)    Finished dev [unoptimized + debuginfo] target(s) in 0.69s     Running `target\debug\cargo_learn.exe`Err(Os { code: 2, kind: NotFound, message: "零碎找不到指定的文件。" })

?操作符,在定义Result值之后搁置的代码,其工作形式与咱们定义为解决上例中的Result值的匹配表达式简直雷同。 如果Result的值是Ok,则Ok内的值将从该表达式中返回,并且程序将持续。如果该值为Err,则将像咱们应用过return关键字一样从整个函数中返回Err,以便将谬误值流传到调用代码。

如此,上述示例能够更简略的表白为:

fn read_username_from_file() -> Result<String, io::Error> {    let mut s = String::new();    File::open("hello.txt")?.read_to_string(&mut s)?;    Ok(s)}

咱们已将s中新String的创立移至函数的结尾;该局部没有扭转。咱们没有创立变量f,而是将对read_to_string的调用间接链接到File::open("hello.txt")?的后果上。咱们还有一个?在read_to_string调用的开端,当File::open和read_to_string都胜利而不返回谬误时,咱们依然返回一个蕴含s中用户名的Ok值。性能再次与上述例子雷同;这只是一种不同的,更符合人体工程学的编写形式。

还有一种更简略的实现形式:

use std::fs;use std::io;fn read_username_from_file() -> Result<String, io::Error> {    fs::read_to_string("hello.txt")}

将文件读入字符串是一个相当常见的操作,因而Rust提供了便捷的fs::read_to_string函数,该函数可关上文件,创立新的String,读取文件内容,将内容放入该String中并返回。 当然,应用fs::read_to_string并不能给咱们解释所有错误处理的机会,因而咱们首先进行了较长的解释。

?运算符可用于返回后果的函数中

?运算符可用于返回类型为Result的函数中,因为其定义的工作形式与上例中定义的match表达式雷同。 匹配的要求返回类型为Result的局部是return Err(e),因而函数的返回类型能够是Result以便与此返回兼容。
让咱们看看如果应用?会产生什么? 您会记得主函数中的运算符的返回类型为():

use std::fs::File;fn main() {    let f = File::open("hello.txt")?;}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `std::ops::Try`) --> src\main.rs:4:13  |3 | / fn main() {4 | |     let f = File::open("hello.txt")?;  | |             ^^^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`5 | | }  | |_- this function should return `Result` or `Option` to accept `?`  |  = help: the trait `std::ops::Try` is not implemented for `()`  = note: required by `std::ops::Try::from_error`error: aborting due to previous errorFor more information about this error, try `rustc --explain E0277`.error: could not compile `cargo_learn`.To learn more, run the command again with --verbose.

这个谬误指出咱们只容许应用?返回Result或Option或实现std::ops::Try的其余类型的函数中的运算符。 当您在不返回这些类型之一的函数中编写代码时,要应用? 当您调用其余返回Result<T, E>的函数时,有两种抉择能够解决此问题。 一种技术是在没有限度的状况下将函数的返回类型更改为Result<T, E>。 另一种技术是应用matchResult<T, E>办法之一以适当的形式解决Result<T, E>

main函数是非凡的,并且其返回类型必须具备限度。 main的一种无效返回类型是(),更不便地,另一种无效返回类型是Result<T, E>,如下所示:

use std::error::Error;use std::fs::File;fn main() -> Result<(), Box<dyn Error>> {    let f = File::open("hello.txt")?;    Ok(())}D:\learn\cargo_learn>cargo run   Compiling cargo_learn v0.1.0 (D:\learn\cargo_learn)warning: unused variable: `f` --> src\main.rs:5:9  |5 |     let f = File::open("hello.txt")?;  |         ^ help: if this is intentional, prefix it with an underscore: `_f`  |  = note: `#[warn(unused_variables)]` on by defaultwarning: 1 warning emitted    Finished dev [unoptimized + debuginfo] target(s) in 0.72s     Running `target\debug\cargo_learn.exe`Error: Os { code: 2, kind: NotFound, message: "零碎找不到指定的文件。" }error: process didn't exit successfully: `target\debug\cargo_learn.exe` (exit code: 1)

应用panic!还是不应用

Rust的错误处理性能旨在帮忙编写更牢靠的代码。 panic!宏示意程序处于无奈解决的状态,可让咱们通知过程进行,而不是尝试应用有效或不正确的值进行解决。Result枚举应用Rust的类型零碎来示意操作可能会以代码能够复原的形式失败。 咱们能够应用Result来通知调用咱们的代码的代码也须要解决潜在的胜利或失败。panic!Result在适当的状况下将使咱们的代码在遇到不可避免的问题时更加牢靠。