Rust的错误处理机制

Rust的错误处理机制

任何语言都需要有错误处理机制,看到主流的语言清一色的try…catch…,Go和Rust又坐不住了,纷纷提出了自己的错误处理机制。Go的错误处理机制被骂的体无完肤,而Rust的错误处理却获赞不少。那么Rust的错误处理有什么独到之处呢,一起来学习一下吧。

Rust还会发生崩溃吗?

即使try…catch…,也有很多不能处理的错误而引发崩溃,是的,任何一种错误处理机制都不可能100%的捕获所有的错误。Rust的内存机制再出色、编译器再强大,也不能保证没有意外的发生。

以最基础的数组越界为例:

fn main() {
    let a = [1, 2, 3, 4, 5];
    println!("{}", a[5]);
}

编译器表现得很好,直接器报错:

error: this operation will panic at runtime
 --> src\main.rs:3:20
  |
3 |     println!("{}", a[5]);
  |                    ^^^^ index out of bounds: the len is 5 but the index is 5
  |
  = note: `#[deny(unconditional_panic)]` on by default

error: aborting due to previous error

很好,那我再换一个写法:

fn main() {
    let a = [1, 2, 3, 4, 5];
    let b = 4;
    println!("{}", a[b+1]);
}

不负众望的编译器还是亮起来红灯:

error: this operation will panic at runtime
 --> src\main.rs:4:20
  |
4 |     println!("{}", a[b+1]);
  |                    ^^^^^^ index out of bounds: the len is 5 but the index is 5
  |
  = note: `#[deny(unconditional_panic)]` on by default

error: aborting due to previous error

看来我小瞧编译器了。那么,再提升一个难度呢?

use rand::Rng;

fn main() {
    let a = [1, 2, 3, 4, 5];
    let b = rand::thread_rng().gen_range(5, 999);	// 生成一个随机数,这回编译器不知道b的值是多少了
    println!("{}", a[b]);
}

果然,编译器不是万能的,这次编译通过了,但运行崩溃了:

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

可见,强大如rust也没有办法摆脱崩溃的烦恼。

错误的分类

Rust将运行时错误分为两大类:

  • 不可恢复的错误
  • 可恢复的错误

对于大部分错误,并没有严重到无方可解的地步,这个时候,Rest采用Result枚举来解决(还记得吗?内置的枚举类型里有过一面之缘);然而,对于某些错误,一旦发生就是致命的,此时,Rust提供了一个叫做panic! 的宏,它会在不可恢复的错误发生之后,清理内存数据,然后停止程序的运行。

不可恢复的错误

使用随机数访问数组这个例子就发生了panic,Rust自动调用了panic! 宏,输出中提到了设置RUST_BACKTRACE=1这个环境变量来查看调用堆栈的信息:

thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 451', src\main.rs:7:20
stack backtrace:
   0: backtrace::backtrace::dbghelp::trace
             at C:\Users\VssAdministrator\.cargo\registry\src\github.com-1ecc6299db9ec823\backtrace-0.3.46\src\backtrace/dbghelp.rs:88
   1: backtrace::backtrace::trace_unsynchronized
             at C:\Users\VssAdministrator\.cargo\registry\src\github.com-1ecc6299db9ec823\backtrace-0.3.46\src\backtrace/mod.rs:66
   2: std::sys_common::backtrace::_print_fmt
             at src\libstd\sys_common/backtrace.rs:78
   3: <std::sys_common::backtrace::_print::DisplayBacktrace as core::fmt::Display>::fmt
             at src\libstd\sys_common/backtrace.rs:59
   4: core::fmt::write
             at src\libcore\fmt/mod.rs:1076
   5: std::io::Write::write_fmt
             at src\libstd\io/mod.rs:1537
   6: std::sys_common::backtrace::_print
             at src\libstd\sys_common/backtrace.rs:62
   7: std::sys_common::backtrace::print
             at src\libstd\sys_common/backtrace.rs:49
   8: std::panicking::default_hook::{{closure}}
             at src\libstd/panicking.rs:198
   9: std::panicking::default_hook
             at src\libstd/panicking.rs:218
  10: std::panicking::rust_panic_with_hook
             at src\libstd/panicking.rs:486
  11: rust_begin_unwind
             at src\libstd/panicking.rs:388
  12: core::panicking::panic_fmt
             at src\libcore/panicking.rs:101
  13: core::panicking::panic_bounds_check
             at src\libcore/panicking.rs:73
  14: guessing::main
             at src/main.rs:7
  15: std::rt::lang_start::{{closure}}
             at C:\Users\zhang\.rustup\toolchains\stable-x86_64-pc-windows-gnu\lib/rustlib/src/rust\src\libstd/rt.rs:67
  16: std::rt::lang_start_internal::{{closure}}
             at src\libstd/rt.rs:52
  17: std::panicking::try::do_call
             at src\libstd/panicking.rs:297
  18: std::panicking::try
             at src\libstd/panicking.rs:274
  19: std::panic::catch_unwind
             at src\libstd/panic.rs:394
  20: std::rt::lang_start_internal
             at src\libstd/rt.rs:51
  21: std::rt::lang_start
             at C:\Users\zhang\.rustup\toolchains\stable-x86_64-pc-windows-gnu\lib/rustlib/src/rust\src\libstd/rt.rs:67
  22: main
  23: _tmainCRTStartup
  24: mainCRTStartup
  25: _report_error
  26: _report_error
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

这个例子太于简单,调用堆栈都是Rust标准库的调用,没有多少有用的信息,如果真正的大型项目,相信还是有用的。

panic! 宏可可以被显式的调用:

fn main() {
    panic!("我要挂了");
}

可恢复的错误

在前面见到Resut枚举时,用了一个例子,打开一个不存在的文件:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => println!("open file success"),
        Err(error) => println!("open file error:{:?}", error)
    };
}

File::open返回的是Resutl枚举,Result枚举实现实现了多个方法,如unwrapexpect,它们可以用来简化match:

use std::fs::File;

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

在hello.txt不存在的情况下,运行时会有以下输出:

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

unwrap内部对open返回的Result进行了模式匹配,如果是Ok,则返回Ok的值,如果是Err,则直接调用panic!。我们希望能看到更多的信息,比如究竟是哪个文件找不到。这时,可以使用expectexpect允许在调用panic!时传入我们自己的信息:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("can not found file: 'hello.txt'");
}

这段代码输出:

thread 'main' panicked at 'can not found file: '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

从编程逻辑上,如果文件不存在,最好的方法是创建它。那么,打开文件失败就一定是文件不存在吗?当然不是,我们可以通过错误类型来判断:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => println!("open file success"),
        Err(error) => match error.kind() {
            ErrorKind::NotFound => println!("file not found"),
            _ => println!("open file error:{:?}", error)
        }
    };
}

前面我们已经知道File::open返回一个Result枚举,我们通过match进行模式匹配。error.kind()也返回一个ErrorKind枚举,我们可以通过模式匹配嵌套的方式进行处理。我们已经可以判断是不是因为文件不存在而失败了,再进一步,就可以进行创建文件了。

但是问题来了,创建就一定能成功吗?显示也是不行的,因此,创建文件的函数也返回一个Result枚举:

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");
    match f {
        Ok(file) => println!("open file success"),
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(file) => println!("create file success"),
                Err(e) => println!("create file error: {}", e),
            },
            _ => println!("open file error:{:?}", error)
        }
    };
}

这段代码可以正常工作,可是三个match嵌套在一起看起来有些不太舒服。我们还可以使用unwrapexpect的变种,以unwrap为例,它有4个变种:

  • unwrap_err:与unwrap,返回Err的值,当Result是Ok时panic

  • unwrap_or(default):如果是Ok,则返回Ok的值,如果是Err返回给定的default

  • unwrap_or_default:如果是Ok,则返回Ok的值,如果是Err返回Ok相应类型的默认值

  • unwrap_or_else():传入一个闭包,如果是Ok,则返回Ok的值,如果是Err则执行这个闭包。我前面写过一篇关于闭包的文章,感兴趣的朋友可以看一下:https://blog.csdn.net/zhmh326/article/details/108443322。

use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt").unwrap_or_else(|e| {
        if e.kind() == ErrorKind::NotFound {
            File::create("hello.txt").unwrap()
        } else {
            panic!("open file error:{:?}", e);
        }
    });
}

传播错误

try…catch…有很好的传播特性,当异常发生时,如果不进行处理,它会传播到调用者那里,由调用者处理。Rust的错误处理同样可以实现错误的传播,不过,它不像try…catch…那样是自动的,而是需要将Result类型作为返回值一层层的返回,直到被处理。

比如我们封装一个从文件中读取数据的函数:

use std::io;
use std::io::Read;
use std::fs::File;

fn read_file(path: &str) -> Result<String, io::Error> {
    let mut f = match File::open(path) {
        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),
    }
}

fn main() {
    println!("file content: {}", read_file("hello.txt").unwrap());
}

read_file函数返回了Result<String, io::Error>类型,在main调用时使用unwrap对其进行处理。事实上,read_file并没有省略对Reuslt的处理,只是将它原样返回了出来。显然这种写法并不友好,这时,可以使用运算符:

use std::io;
use std::io::Read;
use std::fs::File;

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

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

fn main() {
    println!("file content: {}", read_file("hello.txt").unwrap());
}

第6行调用File::open后,接了一个?,不再使用match匹配Result,这样的写法如上面的例子是完全等价的,即当Result为Ok时将文件句柄返回给f,当为Err时,函数直接返回Err。read_to_string也可以这么做,甚至,这两次调用可以使用链式表达:

use std::io;
use std::io::Read;
use std::fs::File;

fn read_file(path: &str) -> Result<String, io::Error> {
    let mut s = String::new();
    File::open(path)?.read_to_string(&mut s)?;
    Ok(s)
}

fn main() {
    println!("file content: {}", read_file("hello.txt").unwrap());
}

当出现错误时,这两个?都有可能返回Err,没有错误时,返回Ok(s)。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值