panic!宏 - 不可恢复的错误
Rust 的可靠性依赖错误处理,Rust 没有类似异常的机制
错误的分类:
可恢复:例如文件没找到,可以再次尝试。使用 Result<T, E>
不可恢复的错误:例如索引超出范围,使用 panic!宏
当 panic! 宏执行,程序会打印一个错误信息,并展开、清理调用栈,最后退出程序
为应对 panic!,展开或中止(abort)调用栈。
默认情况下,panic 发生时 Rust 沿着调用栈往回走、清理每个遇到的函数中的数据。或者立即中止调用栈、不进行清理,直接停止程序,内存需要操作系统进行回收。
想让二进制文件更小,可以把默认的展开改成中止,在 Cargo.toml 文件
rust[dependencies] [profile.release] panic = 'abort'
手动编写 panic,下面这段代码运行 cargo run 时会引起 panic。数组访问越界的时候也会产生 panic
fn main() {
panic!("crash");
}
// thread 'main' panicked at 'crash', src\main.rs:2:3
// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
- 将环境变量设置为 RUST_BACKTRACE=1 展示回溯信息
- Mac 中直接执行 RUST_BACKTRACE=1
- windows 在 cmd 中执行 set RUST_BACKTRACE=1
- windows 在 powershell 中执行 $env:RUST_BACKTRACE=1
Result 枚举
enum Result<T,E> {
//里面有两个变体
Ok(T),
Err(E),
}
- T:操作成功的情况下,Ok 变体里返回的数据的类型
- E:操作失败的情况下,Err 变体里返回的数据的类型
比如我们打开系统的一个文件,有成功和失败两种情况
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file,
Err(error) => {
panic!("文件没找到,错误信息: {:?}", error);
}
};
}
// thread 'main' panicked at '文件没找到,错误信息: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }'
unwrap 方法
match 表达式的一种快捷写法,缺点就是不能自定义调试信息
- 如果 Result 结果是 Ok,返回 Ok 里面的值
- 如果 Result 结果是 Err,调用 panic!宏
上面的那段代码可以像这样写
use std::fs::File;
fn main() {
let f = File::open("hello.txt").unwrap();
}
//thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }'
expect
可以自定义调试信息
use std::fs::File;
fn main() {
let f = File::open("hello.txt").expect("找不到文件 hello.txt");
}
// thread 'main' panicked at '找不到文件 hello.txt: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }'
传播错误
将错误返回给调用者,像标准库的 File::open 和 File::create 等方法的错误都属于传播错误,需要我们自己手动处理
use std::fs::File;
use std::io;
use std::io::Read;
fn read_str_from_file() -> Result<String, io::Error> {
let f = File::open("hello.txt");
let mut f = match f {
Ok(file) => file,
Err(err) => return Err(err),
};
let mut s = String::new();
match f.read_to_string(&mut s) {
Ok(_) => Ok(s),
Err(e) => Err(e),
}
}
fn main() {
// 这里就拿到函数里传播出来的错误
// thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, //message: "系统找不到指定的文件。" }'
let res = read_str_from_file().unwrap();
}
?运算符
传播错误处理的一种快捷写法(语法糖,效果跟上面完全一样)
use std::fs::File;
use std::io;
use std::io::Read;
fn read_str_from_file() -> Result<String, io::Error> {
let mut f = File::open("hello.txt")?;
// let mut f = match f {
// Ok(file) => file,
// Err(err) => return Err(err),
// };
let mut s = String::new();
f.read_to_string(&mut s)?;
// match f.read_to_string(&mut s) {
// Ok(_) => Ok(s),
// Err(e) => Err(e),
// }
Ok(s)
}
fn main() {
let res = read_str_from_file().unwrap();
}
继续优化简洁,使用链式调用的方式简化代码
use std::fs::File;
use std::io;
use std::io::Read;
fn read_str_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 res = read_str_from_file().unwrap();
}
?运算符和 main 函数
?运算符只能用于返回的值类型是 Result 的函数
- main 函数返回类型是()
- main 函数的返回类型也可以是:Result<T, E>
- Box<dyn Error>是 trait 对象,理解为任何可能的错误类型
use std::error::Error;
use std::fs::File;
fn main() -> Result<(), Box<dyn Error>> {
let f = File::open("hello.txt")?;
Ok(())
}
什么时候应该用 panic
总体原则
- 在定义一个可能失败的函数时,优先考虑返回 Result
- 某种情况肯定是不可恢复的,那我们就使用 panic!
比如说我们编写一个代码库,一些逻辑是肯定会引起不可恢复的,那我们可以使用 panic!自己去处理代码错误,其它时候大部分都是要将错误传播出去给调用者自己去处理。
有时候你比编译器知道更多信息的时候,可以确定 Result 就是 Ok:unwrap
use std::net::IpAddr;
fn main() {
// 这里肯定不会出现错误
let ip: IpAddr = "127.0.0.1".parse().unwrap();
}
有时候用户调用我们的函数,传入了无意义的参数,我们可以手动调用 panic!去给用户警告
调用外部不可控的代码时,返回非法的状态无法修复,可以调用 panic!