原文地址 https://doc.rust-lang.org/edition-guide/rust-2018/error-handling-and-panics/index.html
3.2错误处理和恐慌
在本指南的这一章中,我们讨论了Rust中错误处理的一些改进。 其中最值得注意的是引入?运算符。
3.2.1? 操作符可以更轻松地处理
Minimum Rust version: 1.13 for Result<T, E>
Minimum Rust version: 1.22 for Option
Rust已经获得了一个新的操作符?,它通过减少所涉及的视觉噪声使错误处理更加愉快。 它通过解决一个简单问题来做到这一点。 为了说明,假设我们有一些代码来从文件中读取一些数据:
#![allow(unused_variables)]
fn main() {
use std::{io::{self, prelude::*}, fs::File};
fn read_username_from_file() -> Result<String, io::Error> {
let f = File::open("username.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),
}
}
}
注意:通过单次调用std :: fs :: read_to_string可以使这段代码变得更简单,但是我们在这里手动编写它以得到一个包含多个错误的示例。
此代码有两个可能失败的路径,打开文件并从中读取数据。 如果其中任何一个都无法工作,我们想从read_username_from_file返回错误。 这样做涉及匹配I / O操作的结果。 在这种简单的情况下,我们只是在调用堆栈中传播错误,匹配只是样板 - 看到它写出来,每次都以相同的模式,不向读者提供大量有用的信息。
使用?,上面的代码如下所示:
#![allow(unused_variables)]
fn main() {
use std::{io::{self, prelude::*}, fs::File};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = File::open("username.txt")?;
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
}
的? 是我们之前写的整个匹配语句的简写。 换一种说法, ? 适用于Result值,如果是Ok,则将其解包并给出内部值。 如果它是一个Err,它将从您当前所处的函数返回。在视觉上,它更直接。 现在我们只使用单个“?”而不是整个匹配语句。 字符,表示我们以标准方式处理错误,通过将它们传递给调用堆栈。
经验丰富的Rustaceans可能会认识到这与尝试相同! 自Rust 1.0以来一直可用的宏。 事实上,他们是一样的。 以前,read_username_from_file可能是这样实现的:
#![allow(unused_variables)]
fn main() {
use std::{io::{self, prelude::*}, fs::File};
fn read_username_from_file() -> Result<String, io::Error> {
let mut f = try!(File::open("username.txt"));
let mut s = String::new();
try!(f.read_to_string(&mut s));
Ok(s)
}
}
那么为什么在我们已经拥有宏时扩展语言? 原因有很多。 第一次尝试! 事实证明它非常有用,并且常用于惯用的Rust。 经常使用它,我们认为值得拥有一个甜蜜的语法。 这种演变是强大的宏系统的巨大优势之一:语言语法的推测性扩展可以在不修改语言本身的情况下进行原型化和迭代,反过来,结果显示特别有用的宏可以指示缺少语言 特征。 这个演变,来自尝试! 至 ? 是一个很好的例子。
尝试的原因之一! 需要一个更甜的语法是,当多次调用尝试时它是相当没有吸引力的! 连续使用。 考虑:
try!(try!(try!(foo()).bar()).baz())
而不是
foo()?.bar()?.baz()?
第一个是非常难以直观扫描,每个错误处理层都为表达式添加了一个额外的try !. 这会引起过度关注琐碎的错误传播,模糊主代码路径,在本例中调用foo,bar和baz。 这种与错误处理链接的方法发生在构建器模式等情况下。
最后,专用语法将使将来更容易生成专门针对?定制的更好的错误消息,而一般来说很难为宏扩展代码产生好的错误。
您可以使用 ?Result<T, E>s,但也包含Option 。 在这种情况下, ? 将为Some(T)返回一个值,并为None返回None。 目前的一个限制是你不能使用? 对于两者在同一个函数中,因为返回类型需要匹配您使用的类型? 上。 将来,这一限制将被取消。
3.2.2? 在mian和测试中
Minimum Rust version: 1.26
Rust的错误处理围绕返回Result <T,E>并使用? 传播错误。 对于那些编写许多小程序并希望进行多次测试的人来说,一个常见的剪纸一直在混合入口点,例如main和#[test] s,并进行错误处理。例如,您可能试图写:
use std::fs::File;
fn main() {
let f = File::open("bar.txt")?;
}
既然? 通过传播结果并提前返回封闭函数,上面的代码段不起作用,今天导致以下错误:
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:5:13
|
5 | let f = File::open("bar.txt")?;
| ^^^^^^^^^^^^^^^^^^^^^^ cannot use the `?` operator in a function that returns `()`
|
= help: the trait `std::ops::Try` is not implemented for `()`
= note: required by `std::ops::Try::from_error`
要在Rust 2015中解决这个问题,您可能会编写如下内容:
// Rust 2015
use std::process;
use std::error::Error;
fn run() -> Result<(), Box<Error>> {
// real logic..
Ok(())
}
fn main() {
if let Err(e) = run() {
println!("Application error: {}", e);
process::exit(1);
}
}
但是,在这种情况下,run函数具有所有有趣的逻辑,main只是样板。 #[test]的问题更严重,因为它们往往会有更多。
在Rust 2018中,你可以让#[test]和main函数返回一个Result:
// Rust 2018
use std::fs::File;
fn main() -> Result<(), std::io::Error> {
let f = File::open("bar.txt")?;
Ok(())
}
在这种情况下,如果说文件不存在并且某处有Err(错误),那么main将以错误代码(不是0)退出并打印出err的Debug表示。
更多细节
获取 - >结果<…>在main和#[test]的上下文中工作并不神奇。它全部由终止特征支持,主要和测试功能的所有有效返回类型必须实现。特征定义为:
#![allow(unused_variables)]
fn main() {
pub trait Termination {
fn report(self) -> i32;
}
}
在为应用程序设置入口点时,编译器将使用此特征并在您编写的主函数的Result上调用.report()。
结果和()的这个特征的两个简化示例实现是:
#![allow(unused_variables)]
fn main() {
#![feature(process_exitcode_placeholder, termination_trait_lib)]
use std::process::ExitCode;
use std::fmt;
pub trait Termination { fn report(self) -> i32; }
impl Termination for () {
fn report(self) -> i32 {
use std::process::Termination;
ExitCode::SUCCESS.report()
}
}
impl<E: fmt::Debug> Termination for Result<(), E> {
fn report(self) -> i32 {
match self {
Ok(()) => ().report(),
Err(err) => {
eprintln!("Error: {:?}", err);
use std::process::Termination;
ExitCode::FAILURE.report()
}
}
}
}
}
正如您在()的情况中所看到的,只返回成功代码。在Result的情况下,成功案例委托给()的实现,但在Err(…)上打印出错误消息和失败退出代码。
要了解有关更精细细节的更多信息,请参阅跟踪问题或RFC。