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
	} 
}