美文网首页
Rust for cpp devs - 错误处理

Rust for cpp devs - 错误处理

作者: 找不到工作 | 来源:发表于2021-04-13 23:24 被阅读0次

Rust 将软件中的错误分为两个类型:可恢复错误和不可恢复错误。

对于可恢复错误,例如文件找不到,可以报告给调用者处理。而不可恢复错误,例如内存访问越界,则应该直接终止程序。

大部分语言并不区分这两者,而是统一使用 exception 机制。Rust 没有 exception,对于可恢复错误,Rust 使用 Result<T, E> 作为返回值,对于不可恢复错误,用 panic! 宏终止程序。

不可恢复的错误使用 panic!

某些错误是调用者无法修复的,只能让程序终止。这时候我们使用 panic! 宏:

fn main() {
    panic!("crash and burn");
}

错误信息是:

thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Using a panic! Backtrace

我们常常遇到的情况是被调用者 panic,而非手动 panic。如:

fn main() {
    let v = vec![1,2,3];
    v[99];
}

结果是:

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

提示我们可以通过 RUST_BACKTRACE 环境变量来跟踪到底什么触发了错误:

RUST_BACKTRACE=1 cargo run

可恢复错误使用 Result

对于使用方法错误(如用户输入的参数错误),使用 panic! 不是很合适。此外,大部分错误没有严重到需要让程序停止运行。这种情况下可以使用 Result 类型作为返回值:

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

例如,如果我们要打开一个文件,需要处理文件不存在的情况:

use std::fs::File;

fn main() {
    let f = File::open("file.txt");
    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error)
    };
}

这时候如果路径下不存在 file.txt 这个文件,则会报错:

thread 'main' panicked at 'Problem opening the file: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:8:23
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

语法糖:unwrapexpect

上面代码的 pattern 非常常见,因此 Result<T, E> 类型定义了一些方法方法来减少开发者的劳动。

  • unwrap:如果 Ok,返回 Ok 中包含的值,如果 Err,调用 panic!
use std::fs::File;

fn main() {
    let f = File::open("hello.txt").unwrap();
}
  • expect:类似于 unwrap,但是允许我们自定义提示信息。
use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

传递错误

有时我们希望将错误交给调用者处理,此时,我们就需要传递(propagating)这个错误。

use std::fs;
use std::io;
use std::io::Read;
use std::process;

fn read_username_from_file(filename: &str) -> Result<String, io::Error> {
    let f = fs::File::open(filename);

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    let result = match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    };

    return result;
}

fn main() {
    let filename = "a.txt";
    match read_username_from_file(filename) {
        Ok(s) => println!("read from file {} succeeded, {}", filename, s),
        Err(e) => {
            println!("read from file {} failed: {}", filename, e);
            process::exit(1);
        }
    };
}

read_username_from_file 函数内部并不处理错误,而是通过返回一个 Result<T, E> 类型来让调用者处理。在这里,错误类型是 io::Error,可能由 File::openread_to_string 方法引发。当返回错误时,调用者可以根据需求编写处理逻辑,例如使用默认用户名,或是像上面代码一样报错退出。

传递错误的语法糖:? 操作符

传递错误的逻辑非常常见,因此 Rust 提供了 ? 来简化代码。

Result 值后加 ? 的效果是:

  • Ok:返回 Ok 中的值,并继续执行
  • Err:跳出整个函数,并返回错误

因此,上面的函数可以简化为:

fn read_username_from_file(filename: &str) -> Result<String, io::Error> {
    let mut f = fs::File::open(filename)?;

    let mut s = String::new();

    f.read_to_string(&mut s)?;

    return Ok(s);
}

? 操作符已经省略了许多重复代码,它还支持链式调用,更加简洁:

fn read_username_from_file(filename: &str) -> Result<String, io::Error> {
    let mut s = String::new();

    fs::File::open(filename)?.read_to_string(&mut s)?;

    return Ok(s);
}

不指定错误类型

如果我们不关心错误类型,我们可以将io::Error 替换为 Box<dyn std::error::Error>。它可以是任何实现了 Error trait 的类型,约等于所有的错误类型。

fn read_username_from_file(filename: &str) -> Result<String, Box<dyn std::error::Error>> {
    let mut s = String::new();

    fs::File::open(filename)?.read_to_string(&mut s)?;

    return Ok(s);
}

相关文章

  • Rust for cpp devs - 错误处理

    Rust 将软件中的错误分为两个类型:可恢复错误和不可恢复错误。 对于可恢复错误,例如文件找不到,可以报告给调用者...

  • Rust for cpp devs - 线程

    由于 Rust 特有的 ownership 和类型检查机制,许多并发问题都可以在编译期发现,这极大地降低了风险以及...

  • Rust for cpp devs - mutex

    除了 channel[https://www.jianshu.com/p/925d3534ac7f],我们也可以通...

  • Rust for cpp devs - Ownership

    编程语言的内存管理一般有两种: 带垃圾回收机制的,如 Java,Golang,会在运行时检查不再使用的内存并回收,...

  • Rust for cpp devs - channel

    与 golang 一样,Rust 也实现了 channel 用于线程间的通信。如同 golang 的口号一样: D...

  • Rust for cpp devs - closure

    类似于 cpp 中的 lambda 表达式,Rust 中也有 closure。他们与普通函数的区别在于可以捕获变量...

  • Rust for cpp devs - Generic Type

    类似于 cpp,Rust 中也有泛型(generics),用于避免重复的代码。此外,还可以指定 traits 来限...

  • Rust for cpp devs - 迭代器

    迭代器(Iterator)可以允许对序列中的每一个元素执行某个操作。 Rust 的迭代器分为三种: iter() ...

  • Rust for cpp devs - minigrep 项目

    官方文档用 minigrep 项目来讲解如何组织一个 Rust 项目。 保持 main 函数简洁 这样做的好处是:...

  • Rust for cpp devs - 生命周期

    生命周期(lifetime)也是一类泛型。但它是用于指定引用保持有效的作用域。 Rust 中的每个引用都有相应的 ...

网友评论

      本文标题:Rust for cpp devs - 错误处理

      本文链接:https://www.haomeiwen.com/subject/iejhlltx.html