panic!
不可恢复的错误
Rust 错误处理概述
Java 通过try-catch 来处理错误,而在 Rust 并没有类似的异常机制
Rust 的可靠性:错误处理
错误的分类
Rust 没有类似异常的机制
可恢复错误:Result<T, E>
不可恢复:panic! 宏
不可恢复错误与 panic!
当 panic! 宏执行
你的程序会打印一个错误信息
展开(unwind)、清理调用栈(stack)
退出程序
1 2 3 4 fn main () { panic! ("crash and nurn" ); }
展开或中止(abort)调用栈
使用 panic! 产生的回溯信息
panic! 可能会出现在
可通过调用 panic! 的函数的回溯信息来定位引起问题的代码
通过设置环境变量 RUST_BACKTRACE 可得到回溯信息
为了获取带有调式信息的回溯,必须启动调试符号(不带 --release)
Result 与可恢复的错误 Result 枚举 1 2 3 4 enum Result <T, E> { Ok (T), Err (E), }
T:操作成功情况下,OK 变体里返回的数据的类型
E:操作成功情况下,Err 变体里返回的错误的类型
1 2 3 4 5 6 use std::fs::File;fn main () { let f = File::open("hello.txt" ); }
处理 Result 的一种方式:match 表达式
和 Option 枚举一样,Result 及其变体也是有 prelude 带入作用域
1 2 3 4 5 6 7 8 9 10 11 12 use std::fs::File;fn main () { let f = File::open("hello.txt" ); let file = match f { Ok (file) => file, Err (error) => { panic! ("Error opening file {:?}" , error); } }; }
匹配不同的错误 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 use std::fs::File;fn main () { let f = File::open("hello.txt" ); let file = match f { Ok (file) => file, Err (error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt" ) { Ok (fc) => fc, Err (e) => panic! ("Error createing file {:?}" , error), }, other_error => panic! ("Error opening file {:?}" , other_error); }, }; }
上面例子使用了很多 match
match 很有用,但是很原始
闭包(closure),Result<T, E> 有很多方法
它们接收闭包作为参数
使用 match 实现
使用这些方法会让代码更简洁
1 2 3 4 5 6 7 8 9 10 11 12 13 use std::fs::File;fn main () { let f = File::open("hello.txt" ).unwrap_or_else(|error| { if error.kind() == ErrorKind::NotFound { File::create("hello.txt" ).unwrap_or_else(|error| { panic! ("Error createing file {:?}" , error) }) } else { panic! ("Error opening file {:?}" , error) } }); }
unwrap
unwrap:match 表达式的一个快捷方法
如果 Result 结果是 OK,返回 OK 里面的值
如果 Result 结果是 Err,调用 panic! 宏
1 2 3 4 5 6 use std::fs::File;fn main () { let f = File::open("hello.txt" ).unwrap(); }
传播错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 use std::fs::File;use std::io;use std::io::Read;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), } } fn main () { read_username_from_file(); }
? 运算符
? 运算符只能用于返回 Result 的函数
? 运算符:传播错误的一种快捷方式
如果 Result 是 Ok:Ok 中的值就是表达式的结果,然后继续执行程序
如果 result 是 Err:Err 就是整个函数的返回值,就像使用了 return
1 2 3 4 5 6 7 8 9 10 11 12 13 14 use 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) } fn main () { let result = read_username_from_file(); }
如果觉得上述的方式太繁琐,那么可以将代码改成链式调用的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 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) } fn main () { let result = read_username_from_file(); }
? 与 from 函数
Trait std::convert::From 上的 from 函数
被 ? 所应用的错误,会隐式的被 from 函数处理
当 ? 调用from函数时
它所接收的错误类型会被转化为当前函数返回类型所定义的错误类型
用于:针对不同错误原因,返回同一种错误类型
只要每个错误类型实现了转换为所返回的错误类型的 from 函数
? 运算符与 main 函数
何时使用 panic! 总体原则
在定义一个可能失败的函数时,优先考虑返回 Result
否则就 panic!
编写示例、原型代码、测试
可以使用 panic!
演示某些概念:unwrap
原型代码:unwrap、expect
测试:unwrap、expect
肯定 Result 结果是 Ok
你可以确定 Result 就是 Ok:unwrap
1 2 3 4 5 use std::net::IpAddr;fn main () { let home: IpAddr = "127.0.0.1" .parse().unwrap(); }
错误处理的指导性建议
当代码最终可能处于损坏状态时,最后使用 panic!
损坏状态(bad state):某些假设、保证、约定或不可变性被打破
例如非法的值、矛盾的值或空缺的值被传入代码
以及下列中的一条
这种损坏状态并不是预期能够偶然发生的事情
在此之后,你的代码如果处于这种损坏状态就无法运行
在你使用的类型中没有一个好的方法来将这些信息(处于损坏状态)进行编码
场景建议
调用你的代码,传入无意义的参数值:panic!
调用外部不可控代码,返回非法错误,你无法修复:panic!
如果失败是可预期的:Result
当你的代码对值进行操作,首先应该验证这些值:panic!
为验证创建自定义类型
创建新的类型,把验证逻辑放在构造实例的函数里
getter:返回字段数据
这种方式校验很简单,如果函数变多了,那么我们校验代码要重复很多,所以将验证逻辑放在构造实例函数中,会节省代码量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 fn main () { loop { let guess = "32" ; 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" ); continue ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 pub struct Guess { value: i32 } impl Guess { pub fn new (value: i32 ) -> Guess { if guess < 1 || guess > 100 { panic! ("The secret number will be between 1 and 100, got {}" , value); } Guess { value } } pub fn value (&self ) -> i32 { self .value } } fn main () { loop { let guess = "32" ; let guess: i32 = match guess.trim().parse() { Ok (num) => num, Err (_) => continue , }; let guess = Guess::new(guess); } }