错误处理¶
panic!¶
fn drink(beverage: &str) {
// You shouldn't drink too much sugary beverages.
if beverage == "lemonade" { panic!("AAAaaaaa!!!!"); }
println!("Some refreshing {} is all I need.", beverage);
}
fn main() {
drink("water");
drink("lemonade");
}
[!tips] 当出现 panic 时,程序默认会开始 展开(unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。另一种选择是直接 终止(abort),这会不清理数据就退出程序。 那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,panic 时通过在 Cargo.toml 的
[profile]
部分增加panic = 'abort'
,可以由展开切换为终止。例如,如果你想要在 release 模式中 panic 时直接终止:[profile.release] panic = 'abort'
Result¶
Result 的定义¶
它定义有如下两个成员,Ok
和 Err
:
enum Result<T, E> {
Ok(T),
Err(E),
}
T
和 E
是泛型类型参数。By convention, the expected outcome is Ok
while the unexpected outcome is Err
.
use std::fs::File;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => panic!("Problem opening the file: {:?}", error),
};
}
File::open
的返回值是 Result<T, E>
。泛型参数 T
会被 File::open
的实现放入成功返回值的类型 std::fs::File
,这是一个文件句柄。错误返回值使用的 E
的类型是 std::io::Error
。这些返回类型意味着 File::open
调用可能成功并返回一个可以读写的文件句柄。
匹配不同的错误¶
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error);
}
},
};
}
逻辑相同但是使用闭包和 unwrap_or_else
方法的例子:
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
if error.kind() == ErrorKind::NotFound {
File::create("hello.txt").unwrap_or_else(|error| {
panic!("Problem creating the file: {:?}", error);
})
} else {
panic!("Problem opening the file: {:?}", error);
}
});
}
unwrap 和 expect¶
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt").unwrap();
}
use std::fs::File;
fn main() {
let greeting_file = File::open("hello.txt")
.expect("hello.txt should be included in this project");
}
传播错误¶
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let username_file_result = File::open("hello.txt");
let mut username_file = match username_file_result {
Ok(file) => file,
Err(e) => return Err(e),
};
let mut username = String::new();
match username_file.read_to_string(&mut username) {
Ok(_) => Ok(username),
Err(e) => Err(e),
}
}
传播错误的简写:? 运算符¶
use std::fs::File;
use std::io::{self, Read};
fn read_username_from_file() -> Result<String, io::Error> {
let mut username_file = File::open("hello.txt")?; // return Error
let mut username = String::new();
username_file.read_to_string(&mut username)?;
Ok(username) // return Ok
}
Result
值之后的 ?
被定义为与示例 9-6 中定义的处理 Result
值的 match
表达式有着完全相同的工作方式。如果 Result
的值是 Ok
,这个表达式将会返回 Ok
中的值而程序将继续执行。如果值是 Err
,Err
中的值将作为整个函数的返回值,就好像使用了 return
关键字一样,这样错误值就被传播给了调用者。
match
表达式与 ?
运算符所做的有一点不同:?
运算符所使用的错误值被传递给了 from
函数,它定义于标准库的 From
trait 中,其用来将错误从一种类型转换为另一种类型。当 ?
运算符调用 from
函数时,收到的错误类型被转换为由当前函数返回类型所指定的错误类型。这在当函数返回单个错误类型来代表所有可能失败的方式时很有用,即使其可能会因很多种原因失败。
[!tips] '?'运算符可以用在实现了
std::ops::Try
trait的类型上。这个运算符可以用于在发生错误时提前返回Result
类型的值。具体来说,'?'运算符可以用在以下情况下:
函数返回类型为
Result<T, E>
的函数中,使用'?'可以方便地将发生错误时的Err
值返回给调用者。在实现了
std::ops::Try
trait的类型的方法中,使用'?'可以将可能发生的错误传播给调用者。不过,'?'运算符不能用在以下情况下:
在没有返回
Result
类型的函数中,使用'?'将会导致编译错误。在没有实现
std::ops::Try
trait的类型中,使用'?'也会导致编译错误。
main函数返回错误
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let greeting_file = File::open("hello.txt")?;
Ok(())
}