Skip to content

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

rust
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 枚举

rust
enum Result<T,E> {
    //里面有两个变体
    Ok(T),
    Err(E),
}
  • T:操作成功的情况下,Ok 变体里返回的数据的类型
  • E:操作失败的情况下,Err 变体里返回的数据的类型

比如我们打开系统的一个文件,有成功和失败两种情况

rust
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!宏

上面的那段代码可以像这样写

rust
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

可以自定义调试信息

rust
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 等方法的错误都属于传播错误,需要我们自己手动处理

rust
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();
}

?运算符

传播错误处理的一种快捷写法(语法糖,效果跟上面完全一样)

rust
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();
}

继续优化简洁,使用链式调用的方式简化代码

rust
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 对象,理解为任何可能的错误类型
rust
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

rust
use std::net::IpAddr;

fn main() {
  // 这里肯定不会出现错误
  let ip: IpAddr = "127.0.0.1".parse().unwrap();
}

有时候用户调用我们的函数,传入了无意义的参数,我们可以手动调用 panic!去给用户警告

调用外部不可控的代码时,返回非法的状态无法修复,可以调用 panic!

每天进步一丢丢