乐趣区

关于rust:rust学习错误处理

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:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: 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:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: 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 error

For 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:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: 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 default

warning: 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:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: 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 default

warning: 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:13
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: 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 error

For 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 default

warning: 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 在适当的状况下将使咱们的代码在遇到不可避免的问题时更加牢靠。

退出移动版