Rust学习笔记(八)错误处理
Rust学习笔记 (八)错误处理
#ProgrammingLanguage/Rust
rust将error归类为两种,可回收的和不可回收的。 例如,文件因找不到而无法打开的错误,就是可回收错误。超界访问array,就是不可回收错误。
rust没有exceptions,取而代之的是,Result<T , E>类型用于可回收错误,并且使用 panic!宏,在程序出现不可回收错误的时候暂停程序。
unrecoverable errors with panic 如果你希望你最后的rust编译出一个尽量小的二进制文件,可以在Cargo.toml文件中配置
[profile.release]
panic = 'abort'
就会在程序panic的时候,用abort操作替代unwinding。
使用backtrace
RUST_BACKTRACE=1 cargo run
recoverable errors with result
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)
}
},
};
}
⚠️Err(ref error) 和 Err(error),前者为什么使用引用,是因为所有权的问题,如果前者不使用引用,第二个Err(error),中的error会不可用。
⚠️在pattern中用ref创建一个引用,而不用&的原因是,&匹配引用并返回引用的值,而ref匹配一个值,并提供该值的引用。
propagating errors
use std::io;
use std::io::Read;
use std::fs::File;
fn read_username_from_file() -> Result<String, io::Error>{
let f = File::open("hello.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), }
}
a shortcut for propagating errors: the ? operator
use std::io;
use std::io::Read;
use std::fs::File;
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)
}
use std::io;
use std::io::Read;
use std::fs::File;
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)
}
⚠️?只能用于返回值为Result的函数,main()中使用?报错。
什么时候使用panic! 当使用panic!的时候,代码是没有办法恢复的,你可以在任何错误的场景下使用Panic!,不管是什么类型的错误,但是这样会让你代替调用你函数的程序作出panic!这个操作,是程序不可恢复。 当你选择使用Result作为函数返回值的时候,你就给调用方提供了选择,而不是代替其作出选择。 调用方,是可以选择使用可恢复的方式处理这种情况,或者决定返回的Err值是一个不可恢复回收的值,调用panic!。 所以选择Result类型作为返回值,对于一个可能是失败的函数,是一个不错的选择。
极少数的情况下,选择panic作为返回值更为恰当。
例子,代码原型和测试 创建代码原型的时候,推荐使用unwrap 和 expect。
在你比编译器有跟多信息需要处理的时候 当你有别的逻辑,并且去定Result将会返回OK的时候,使用unwrap也是合适的,但是你的逻辑不是编译器能理解的,比方说ip地址是否合法。 如果您可以通过手动检查代码来确保自己永远不会有Err,那么调用unwrap是完全可以接受的。
use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();
虽然人类的判断是可以认为上面的代码使用unwrap是没问题的,因为“127.0.0.1”是一个固定字符串,代编一个合法ip地址。但是parse方法会返回一个Result类型,编译器仍然认为,可能会返回一个Err。如果IP地址字符串不是一个固定字符串,处理该字符串的时候,可能会失败返回Err的话,我们肯定需要去处理Result的所有情况,让代码更为健壮。
错误处理指导准则 如果你的代码最终会陷入到一种错误的状态,建议还是使用panic. 错误的状态是指,某些假设,保证,合同式的或者不变的定式,已经被破坏,例如无效值,矛盾值,或者缺失值传递到你的代码的时候。
- 你的这种错误状态,不是偶然发生,可复现。
- 你后续代码的正确执行与否,依赖于你目前可能是错误状态的条件。
- 没有很好的方法,将信息编码为你使用的类型。
如果调用方传入的参数,不合法,建议使用panic。
创建用于验证的自定义类型
loop {
// --snip--
let guess: i32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
if guess < 1 || guess > 100 {
println!("The secret number will be between 1 and 100."); ontinue;
}
match guess.cmp(&secret_number) {
// --snip-- }
我们可以创建一个新类型并将验证放入函数中 创建类型的实例,而不是在每个位置重复进行验证。
自定义一个需要的类型:
pub struct Guess {
value: u32,
}
impl Guess {
pub fn new(value: u32) -> Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100, got {}.", value
);
}
Guess {
value
}
}
pub fn value(&self) -> u32 {
self.value
}
}