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)
}
Result 值之后的 ? 被定义为与示例 9-6 中定义的处理 Result 值的 match 表达式有着完全相同的工作方式。如果 Result 的值是 Ok,这个表达式将会返回 Ok 中的值而程序将继续执行。如果值是 Err,Err 中的值将作为整个函数的返回值,就好像使用了 return 关键字一样,这样错误值就被传播给了调用者。
运算符所使用的错误值被传递给了from 函数,它定义于标准库的 From trait 中,其用来将错误从一种类型转换为另一种类型。当 ? 运算符调用 from 函数时,收到的错误类型被转换为由当前函数返回类型所指定的错误类型。这在当函数返回单个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。只要每一个错误类型都实现了 from 函数来定义如何将自身转换为返回的错误类型,? 运算符会自动处理这些转换。
在示例 9-7 的上下文中,File :: open 调用结尾的 ? 将会把 Ok 中的值返回给变量 f。如果出现了错误,? 运算符会提早返回整个函数并将一些 Err 值传播给调用者。同理也适用于 read_to_string 调用结尾的 ?。? 运算符消除了大量样板代码并使得函数的实现更简单。
use std::io;
use std::io::Read;
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 被放到了函数开头;这一部分没有变化。我们对 File :: open(”hello.txt”)? 的结果直接链式调用了 read_to_string,而不再创建变量 f。仍然需要 read_to_string 调用结尾的 ?,而且当File :: open 和 read_to_string 都成功没有失败时返回包含用户名 s 的 Ok 值。
说到编写这个函数的不同方法,甚至还有一个更短的写法:
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
fs::read_to_string("hello.txt")
}
哪里可以使用 ? 运算符
? 运算符只能被用于返回值与 ? 作用的值相兼容的函数。因为 ? 运算符被定义为从函数中提早返回一个值,这与示例 9-6 中的 match 表达式有着完全相同的工作方式。示例 9-6 中 match 作用于一个Result 值,提早返回的分支返回了一个 Err(e) 值。函数的返回值必须是 Result 才能与这个 return 相兼容。
fn main() {
let f = File::open("hello.txt")?;
}
这段代码打开一个文件,这可能会失败。? 运算符作用于 File :: open 返回的 Result 值,不过 main 函数的返回类型是 () 而不是 Result。当编译这些代码,会得到如下错误信息:
Compiling error-handling v0.1.0 (file:///projects/error-handling)
error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
--> src/main.rs:4:36
|
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 `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`
For more information about this error, try `rustc --explain E0277`.
error: could not compile `error-handling` due to previous error
这个错误指出只能在返回 Result 或者其它实现了 FromResidual 的类型的函数中使用 ? 运算符。为了修复这个错误,有两个选择。一个是,如果没有限制的话将函数的返回值改为 Result<T, E>。另一个是使用 match 或 Result<T, E> 的方法中合适的一个来处理 Result<T, E>。
错误信息也提到 ? 也可用于 Option 值。如同对 Result 使用 ? 一样,只能在返回 Option 的函数中对 Option 使用 ?。在 Option 上调用 ? 运算符的行为与 Result<T, E> 类似:如果值是 None,此时None 会从函数中提前返回。如果值是 Some,Some 中的值作为表达式的返回值同时函数继续。
text.lines().next()?.chars().last()
}
#
# fn main() {
# assert_eq!(
# last_char_of_first_line("Hello, world\nHow are you today?"),
# Some('d')
# );
#
# assert_eq!(last_char_of_first_line(""), None);
# assert_eq!(last_char_of_first_line("\nhi"), None);
# }
这个函数返回 Option 因为它可能会在这个位置找到一个字符,也可能没有字符。这段代码获取text 字符串 slice 作为参数并调用其 lines 方法,这会返回一个字符串中每一行的迭代器。因为函数希望检查第一行,所以调用了迭代器 next 来获取迭代器中第一个值。如果 text 是空字符串,next 调用会返回 None,此时我们可以使用 ? 来停止并从 last_char_of_first_line 返回 None。如果 text 不是空字符串,next 会返回一个包含 text 中第一行的字符串 slice 的 Some值。
? 会提取这个字符串 slice,然后可以在字符串 slice 上调用 chars 来获取字符的迭代器。我们感兴趣的是第一行的最后一个字符,所以可以调用 last 来返回迭代器的最后一项。这是一个 Option,因为有可能第一行是一个空字符串,例如 text 以一个空行开头而后面的行有文本,像是 ”\nhi”。不过,如果第一行有最后一个字符,它会返回在一个 Some 成员中。? 运算符作用于其中给了我们一个简介的表达这种逻辑的方式。如果我们不能在 Option 上使用 ? 运算符,则不得不使用更多的方法调用或者 match 表达式
来实现这些逻辑。
注意你可以在返回 Result 的函数中对 Result 使用 ? 运算符,可以在返回 Option 的函数中对 Option 使用 ? 运算符,但是不可以混合搭配。? 运算符不会自动将 Result 转化为 Option,反之亦然;在这些情况下,可以使用类似 Result 的 ok 方法或者 Option 的 ok_or 方法来显式转换。
目前为止,我们所使用的所有 main 函数都返回 ()。main 函数是特殊的因为它是可执行程序的入口点和退出点,为了使程序能正常工作,其可以返回的类型是有限制的。
幸运的是 main 函数也可以返回 Result<(), E>,示例 9-12 中的代码来自示例 9-10 不过修改了 main 的返回值为 Result<(), Box> 并在结尾增加了一个 Ok (()) 作为返回值。这段代码可以编译:
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
Box 类型是一个 trait 对象(trait object)第十七章 ” 为使用不同类型的值而设计的 trait 对象” 部分会做介绍。目前可以将 Box 理解为 ” 任何类型的错误”。在返回 Box 错误类型 main 函数中对 Result 使用 ? 是允许的,因为它允许任何 Err 值提前返回。
当 main 函数返回 Result<(), E>,如果 main 返回 Ok (()) 可执行程序会以 0 值退出,而如果 main 返回Err 值则会以非零值退出;成功退出的程序会返回整数 0,运行错误的程序会返回非 0 的整数。Rust 也会从二进制程序中返回与这个惯例相兼容的整数。
main 函数也可以返回任何实现了 std:: process::Termination trait 的类型。截至本书编写时,Terminationtrait 是一个不稳定功能(unstable feature),只能用于 Nightly Rust 中,所以你不能在稳定版 Rust(Stable Rust)中用自己的类型去实现,不过有朝一日应该可以!
现在我们讨论过了调用 panic! 或返回 Result 的细节,是时候回到他们各自适合哪些场景的话题了。