1、panic!不可恢复错误
Rust 错误处理概述
Rust的可靠性:错误处理
大部分情况下:在编译时提示错误,并处理
错误的分类:
可恢复
例如文件未找到,可再次尝试
不可恢复
bug,例如访问的索引超出范围
Rust没有类似异常的机制
可恢复错误:Result<T,E>
不可恢复:panic!宏
不可恢复的错误与panic!
当panic!宏执行:
你的程序会打印一个错误信息
展开(unwind)、清理调用栈(Stack)―退出程序
为应对panic,展开或中止( abort)调用栈
默认情况下,当panic 发生:
程序展开调用栈(工作量大)
Rust沿着调用栈往回走
清理每个遇到的函数中的数据
或立即中止调用栈:
不进行清理,直接停止程序
内存需要OS进行清理
想让二进制文件更小,把设置从“展开”改为“中止”:
在Cargo.toml中适当的profile部分设置:
panic = "abort"
[package]
name = "guessing_game"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "0.5.5"
[profile.release]
panic = "abort"
在main函数中:
use core::panic;
fn main() {
panic!("crash and burn");
}
数组越界问题:
fn main() {
let a = vec![1,2,3];
a[99];
}
set RUST_BACKTRACE=1 && cargo run
如果在vscode中进行设置的话,就会出现一个问题
使用panic!产生的回溯信息
panic!可能出现在:
我们写的代码中
我们所依赖的代码中
可通过调用panic!的函数的回溯信息来定位引起问题的代码
通过设置环境变量RUST_BACKTRACE可得到回溯信息
为了获取带有调试信息的回溯,必须启用调试符号(不带--release)
2、Result 与可恢复的错误
Result枚举
enum Result<T, E> {
Ok(T),
Err(E),
}
T:操作成功情况下,Ok 变体里返回的数据的类型
E:操作失败情况下,Err变体里返回的错误的类型
处理Result的一种方式: match表达式
和Option枚举一样,Result及其变体也是有prelude 带入作用域
匹配不同的错误
use std::{error::Error, fs::File, 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!("Error creating file {:?}",e),
},
orther_error => panic!("Error opening the file: {:?}",orther_error),
}
};
}
上述代码中如果没有这个文件就创建一个文件
闭包( closure) 。Result<T,E>有很多方法:
它们接收闭包作为参数
使用match 实现
使用这些方法会让代码更简洁
use core::panic;
use std::{ fs::File, 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!("Error creating file {:?}",e),
// },
// orther_error => panic!("Error opening the file: {:?}",orther_error),
// }
// };
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 creating file {:?}",error);
})
} else {
panic!("Error opening file: {:?}",error);
}
});
}
unwrap
unwrap: match表达式的一个快捷方法:
use std::{fs::File};
fn main() {
let f = File::open("hello.txt").unwrap();
}
如果Result 结果是Ok,返回Ok 里面的值
如果Result结果是Err,调用panic!宏
但是这个有一个缺点,就是无法指定错误的信息
expect
expect:和 unwrap 类似,但可指定错误信息
use std::{fs::File};
fn main() {
let f = File::open("hello.txt").expect("无法打开文件 hello.txt");
}
3、传播错误
在函数出处理错误将错误
返回给调用者
use std::{ fs::File, io::{self, Read}};
fn main() {
let result = read_username_from_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),
}
}
?运算符
?运算符:传播错误的一种快捷方式
use std::{ fs::File, io::{self, Read}};
fn main() {
let result = read_username_from_file();
}
fn read_username_from_file()->Result<String,io::Error> {
let mut f = File::open("hello.txt")?;
// let mut f = match f {
// Ok(file)=>file,
// Err(e)=>return Err(e),
// };
let mut s = String::new();
f.read_to_string(&mut s)?;
Ok(s)
}
如果Result是Ok:Ok中的值就是表达式的结果,然后继续执行程序
如果Result是Err:Err就是整个函数的返回值,就像使用了return
?与from函数
Trait std::convert::From 上的from函数:
用于错误之间的转换
被?所应用的错误,会隐式的被from函数处理
当?调用from函数时:
它所接收的错误类型会被转化为当前函数返回类型所定义的错误类型
用于:针对不同错误原因,返回同一种错误类型
只要每个错误类型实现了转换为所返回的错误类型的 from函数
?运算符
链式调用的方式
use std::{ fs::File, io::{self, Read}};
fn main() {
let result = read_username_from_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函数
main函数返回类型是:()
use std::{ fs::File, io::{self, Read}};
fn main() {
let f = File::open("hello.txt")?;
}
这里加了?就报错了,那是因为main函数的返回值不是result或者option
main函数的返回类型也可以是:Result<T,E>
use std::{ error::Error, fs::File, io::{self, Read}};
fn main() ->Result<(),Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
Box<dyn Error>是trait对象
简单理解:“任何可能的错误类型”
这个Box暂时可以不用知道是啥,只需要知道它处理错误就行了
4、什么时候应该用panic!
总体原则
在定义一个可能失败的函数时,优先考虑返回Result
否则就panic!
编写示例、原型代码、测试
可以使用panic!
演示某些概念: unwrap
原型代码: unwrap、expect
测试: unwrap、expect
有时你比编译器掌握更多的信息
你可以确定Result就是Ok: unwrap
use std::net::IpAddr;
fn main() {
let home:IpAddr = "127.0.0.1".parse().unwrap();
}
错误处理的指导性建议
当代码最终可能处于损坏状态时,最好使用panic!
损坏状态(Bad state) :某些假设、保证、约定或不可变性被打破
例如非法的值、矛盾的值或空缺的值被传入代码
以及下列中的一条:
这种损坏状态并不是预期能够偶尔发生的事情。
在此之后,您的代码如果处于这种损坏状态就无法运行。
在您使用的类型中没有一个好的方法来将这些信息(处于损坏状态)进行编码。
场景建议
调用你的代码,传入无意义的参数值:panic!
调用外部不可控代码,返回非法状态,你无法修复: panic!
如果失败是可预期的:Result
当你的代码对值进行操作,首先应该验证这些值:panic!
为验证创建自定义类型
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;
}
}
}
这个例子就是我们之前写的猜数游戏的例子,现在我们通过可恢复的错误进行处理
创建新的类型,把验证逻辑放在构造实例的函数里。
fn main() {
loop {
// ..
let guess = "32";
let guess:i32 = match guess.trim().parse() {
Ok(num)=>num,
Err(_)=>continue,
};
let guess = Guess::new(guess);
}
}
pub struct Guess {
value:i32
}
impl Guess {
pub fn new(value:i32)->Guess {
if value < 1 || value > 100 {
panic!("Guess value must be between 1 and 100,got {}",value);
}
Guess{
value
}
}
pub fn value(&self)->i32 {
self.value
}
}
这样的话,程序一旦出现错误就会发生panic!