The Rust Programming Language:Error Handling
Rust 没有异常。相反,它具有可恢复错误的类型 Result<T, E>
和在程序遇到不可恢复错误时停止执行的 panic!
宏。
Unrecoverable Errors with panic!
有两种可以触发 panic 的方式:做能让代码触发 panic 的动作(例如数组越界)或显式地调用panic!
宏。默认地,这些 panic 会打印错误信息,清理栈,然后退出。通过环境变量,你可以让 Rust 在 panic 的时候显示调用栈信息便于调试。
Unwinding the Stack or Aborting in Response to a Panic
默认情况下,当发生崩溃时,程序会开始展开,这意味着Rust会沿着栈往回走,并清理它遇到的每个函数中的数据。然而,往回走并清理是非常繁琐的工作。因此,Rust允许你选择立即中止的替代方案,这将结束程序而不进行清理。
程序使用的内存将需要由操作系统来清理。如果在你的项目中你需要使生成的二进制文件尽可能小,你可以通过在Cargo.toml文件中适当的[profile]部分添加panic = ‘abort’来从展开切换到在崩溃时中止。例如,如果你想在release下崩溃时中止,则添加以下内容:
1 | [profile.release] |
尝试在程序中调用panic!
:
1 | fn main() { |
当运行程序时,你可以看到:
1 | $ cargo run |
另一个 case:
1 | fn main() { |
运行则会报错:
1 | $ cargo run |
如果设置 RUST_BACKTRACE=1
:
1 | $ RUST_BACKTRACE=1 cargo run |
Recoverable Errors with Result
Result
枚举定义了两个枚举值:
1 | enum Result<T, E> { |
调用一个返回Result
值的函数,因为这个函数可能会失败:
1 | use std::fs::File; |
用 match
来处理 Result
:
1 | use std::fs::File; |
Matching on Different Errors
对于不同的错误进行不同的处理:
1 | use std::fs::File; |
Alternatives to Using
match
withResult<T, E>
可以看到,每次都要用一大堆match
来匹配每个 case。可以用闭包和unwrap_or_else
函数结合的方式来更简易地实现:
1 | use std::fs::File; |
Shortcuts for Panic on Error: unwrap
and expect
match
太冗长了,可以用快捷方法wrap
,如果Result
是Ok
,那么它会返回Ok
中包含的值;否则会为我们调用panic!
宏:
1 | use std::fs::File; |
如果不存在这个文件,则会 panic:
1 | thread 'main' panicked at src/main.rs:4:49: |
类似地,expect
方法也能让我们自定义panic!
的错误信息:
1 | use std::fs::File; |
unwrap
和expect
的区别其实就是expect
可以自定义 panic 信息、
此时的报错信息为:
1 | thread 'main' panicked at src/main.rs:5:10: |
Propagating Errors
1 | use std::fs::File; |
A Shortcut for Propagating Errors: The ?
Operator
1 | use std::fs::File; |
Err
会提前 return。
与 match
不同的是:对于 err 的情况会调用在 From
特性上定义的 from
方法,这会将值从一个类型转换到另一个类型。当 ?
操作符调用了 from
函数,那么它就会将接收到的错误类型转换为方法中需要返回的错误类型。
还可以写得更简单:
1 | use std::fs::File; |
还能再简化:
1 | use std::fs; |
Where the ?
Operator Can Be Used
如果我们在 main
函数中使用 ?
操作符:
1 | use std::fs::File; |
这里就不合适了,因为 ?
会返回一个 Result
,而 main
的返回值类型是 ()
,报错信息:
1 | $ cargo run |
错误信息中提到 ?
也可以被用于 Option<T>
。跟在Result
上使用 ?
一样,你也只能在返回 Option
的方法中使用 ?
。它们的行为也是类似的,如果值是 None
那么就会提前返回 None
,例如:
1 | fn last_char_of_first_line(text: &str) -> Option<char> { |
但是你不能混合使用 ?
,不能同时返回 Option
和 Result
。
main
函数其实也可以返回 Result<(), E>
,因此我们可以把上述代码修改为:
1 | use std::error::Error; |
Box<dyn Error>
是一个 trait object,这会在后面讲到。目前你可以认为 Box<dyn Error>
意味着任何类型的错误。