Rust 中的错误主要分为可恢复错误与不可恢复错误。Result<T, E>
用于可恢复错误,panic!
用于不可恢复错误。panic可分为被动触发和主动调用,被动触发指的时由于代码本身原因发生致命错误,如数组访问越界。主动触发是调用panic!
宏,当调用执行该宏时,程序会打印出一个错误信息,展开报错点往前的函数调用堆栈,最后退出程序。
fn main() {
panic!("crash and burn");
}
当出现 panic!
时,程序提供了栈展开与直接终止两种方式来处理终止流程,默认的方式就是 栈展开
,使用直接终止的方式,修改 Cargo.toml
文件,
[profile.release]
panic = 'abort'
Result<T, E>
枚举类型用来表示函数的返回结果。泛型参数 T
代表成功时存入的正确值的类型,存放方式是 Ok(T)
,E
代表错误时存入的错误值,存放方式是 Err(E)。
enum Result<T, E> {
Ok(T),
Err(E),
}
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let f = File::open("hello.txt");
let f = match f {
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),
},
};
}
对打开文件后的 Result<T, E>
类型进行匹配取值,如果是成功,则将 Ok(file)
中存放的的文件句柄 file
赋值给 f
,如果失败,且是文件不存在错误 ErrorKind::NotFound
,就创建文件,这里创建文件File::create
也是返回 Result
,因此继续用 match
对其结果进行处理:创建成功,将新的文件句柄赋值给 f
,如果失败,则 panic,其他
的错误,一律 panic。
对于返回结果Result,除了使用match匹配处理,还可以使用unwrap
和 expect。
它们的作用就是,如果返回成功,就将 Ok(T)
中的值取出来,如果失败,就直接 panic。expect
跟 unwrap
很像,也是遇到错误直接 panic
, 但是会带上自定义的错误提示信息,相比之下,expect
相比 unwrap
能提供更精确的错误信息。
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
let fe = File::open("hello.txt").expect("Failed to open hello.txt");
}
如果调用这段代码时 hello.txt 文件不存在,那么 unwrap
就将直接 panic,expect则会带上错误提示信息panic。
unwrap_or_else
是定义在 Result<T,E>
上的常用方法,如果 Result
是 Ok
,那该方法就类似 unwrap
:返回 Ok
内部的值;如果是 Err
,就调用闭包中的自定义代码对错误进行进一步处理
// 对 build 返回的 `Result` 进行处理
let config = Config::build(&args).unwrap_or_else(|err| {
println!("Problem parsing arguments: {err}");
process::exit(1);
});
在实际处理错误的情景中,有时需要将错误层层传递,其中?H宏可以使代码简化
se std::fs::File;
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)
}
其中?
的作用跟上面的 match
几乎一模一样,如果结果是 Ok(T)
,则把 T
赋值给 f
,如果结果是 Err(E)
,则返回该错误,所以 ?
特别适合用来传播错误。
let mut f = match f {
// 打开文件成功,将file句柄赋值给f
Ok(file) => file,
// 打开文件失败,将错误返回(向上传播)
Err(e) => return Err(e),
};
?不仅可以自动进行类型转换,只要函数返回的错误 ReturnError
实现了 From<OtherError>
特征,那么 ?
就会自动把 OtherError
转换为 ReturnError
。?
还能实现链式调用。例如,上面代码还可以简化写成:
use std::fs::File;
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)
}
File::open
遇到错误就返回,没有错误就将 Ok
中的值取出来用于下一个方法调用;当然,还可以进一步简化,也是最常用的操作方式:
use std::fs;
use std::io;
fn read_username_from_file() -> Result<String, io::Error> {
// read_to_string是定义在std::io中的方法,因此需要在上面进行引用
fs::read_to_string("hello.txt")
}
Rust 标准库为我们提供了 fs::read_to_string
函数,该函数内部会打开一个文件、创建 String
、读取文件内容最后写入字符串并返回。
?也用于Option枚举的返回。Result
通过 ?
返回错误, Option
就通过 ?
返回 None,?
操作符需要一个变量来承载正确的值,只有错误值能直接返回,正确的值不行,因此 ?
只能用于以下形式:
let v = xxx()?;
xxx()?.yyy()?;
pub enum Option<T> {
Some(T),
None
}
fn last_char_of_first_line(text: &str) -> Option<char> {
text.lines().next()?.chars().last()
}
.next
方法返回的是 Option
类型:如果返回 Some(&str)
,那么继续调用 chars
方法,如果返回 None
,则直接从整个函数中返回 None
,不再继续进行链式调用。
fn open_file() -> Result<File, Box<dyn std::error::Error>> {
let mut f = File::open("hello.txt")?;
Ok(f)
}
File::open
报错时返回的错误是 std::io::Error
类型,但是 open_file
函数返回的错误类型是 std::error::Error
的特征对象,可以看到一个错误类型通过 ?
返回后,变成了另一个错误类型。根本原因是在于标准库中定义的 From
特征,该特征有一个方法 from
,用于把一个类型转成另外一个类型,?
可以自动调用该方法,然后进行隐式类型转换。Box<dyn std::error::Error>是一个Error
的特征对象,它表示函数返回一个类型,该类型实现了 Error
特征,这样我们就无需指定具体的错误类型,其它函数也可以返回这个特征对象。