专业的编程技术博客社区

网站首页 > 博客文章 正文

Rust 学习-16-panic!宏(rust宏吧)

baijin 2024-09-11 00:55:23 博客文章 5 ℃ 0 评论

Panic! 是什么

突然有一天,代码出问题了,而你对此束手无策。对于这种情况,Rust 有 panic!宏。在实践中有两种方法造成 panic:执行会造成代码 panic 的操作(比如访问超过数组结尾的内容)或者显式调用 panic! 宏。这两种情况都会使程序 panic。通常情况下这些 panic 会打印出一个错误信息,展开并清理栈数据,然后退出。通过一个环境变量,你也可以让 Rust 在 panic 发生时打印调用堆栈(call stack)以便于定位 panic 的原因。

可以通过配置Cargo.toml 中来设定release模式下panic时直接终止:

 [profile.release]
 panic = 'abort'

直接使用panic!宏

 fn main() {
     panic!("crash and burn");
 }

程序运行时会出现:

 $ cargo run
    Compiling panic v0.1.0 (file:///projects/panic)
     Finished dev [unoptimized + debuginfo] target(s) in 0.25s
      Running `target/debug/panic`
 thread 'main' panicked at 'crash and burn', src/main.rs:2:5
 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
 

信息会显示出错的位置等信息。

使用panic!的backtrace

 fn main() {
     let v = vec![1, 2, 3];
 
     v[99];
 }

使用 backtrace来运行

 $ RUST_BACKTRACE=1 cargo run
 thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5
 stack backtrace:
    0: rust_begin_unwind
              at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5
    1: core::panicking::panic_fmt
              at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14
    2: core::panicking::panic_bounds_check
              at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5
    3: <usize as core::slice::index::SliceIndex<[T]>>::index
              at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10
    4: core::slice::index::<impl core::ops::index::Index<I> for [T]>::index
              at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9
    5: <alloc::vec::Vec<T,A> as core::ops::index::Index<I>>::index
              at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9
    6: panic::main
              at ./src/main.rs:4:5
    7: core::ops::function::FnOnce::call_once
              at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5
 note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
 

通过backtrace的信息,以便查询什么值在什么地方引起了panic。

使用Result处理可以恢复的错误

大部分错误并没有严重到停止执行。

Result 是一个枚举

 enum Result<T, E> {
     Ok(T),
     Err(E),
 }

例如:

当开启一个文件

 use std::fs::File;
 
 fn main() {
     let greeting_file_result = File::open("hello.txt");
 
     let greeting_file = match greeting_file_result {
         Ok(file) => file,
         Err(error) => panic!("Problem opening the file: {:?}", error),
     };
 }
 

File::open 函数返回一个Result的类型, 之后通过match的方式检查并主动panic

优化继续:

 use std::fs::File;
 use std::io::ErrorKind;
 
 fn main() {
     let greeting_file_result = File::open("hello.txt");
 
     let greeting_file = match greeting_file_result {
         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);
             }
         },
     };
 }
 

使用了两个macth, 主要用于当不存在的时候, 创建该文件。 如果创建文件仍然失败,则panic。

优化继续:使用unwrap_or_else

 use std::fs::File;
 use std::io::ErrorKind;
 
 fn main() {
     let greeting_file = File::open("hello.txt").unwrap_or_else(|error| {
         if error.kind() == ErrorKind::NotFound {
             File::create("hello.txt").unwrap_or_else(|error| {
                 panic!("Problem creating the file: {:?}", error);
             })
         } else {
             panic!("Problem opening the file: {:?}", error);
         }
     });
 }

Unwrap_or_else 可以理解为解包,如果 Err 则处理error相关内容。

Unwrap 如果值是Result成员ok, 则返回Ok的值。

如果值是Resute成员Error, unwrap 会为我们调用painc。

 use std::fs::File;
 
 fn main() {
     let greeting_file = File::open("hello.txt").unwrap();
 }
 

当我们希望自定义panic的内容, 更好定位错误时, 可用expect

 use std::fs::File;
 
 fn main() {
     let greeting_file = File::open("hello.txt")
         .expect("hello.txt should be included in this project");
 }

传播错误

当写一个函数时, 除了函数内的错误外, 还希望调用方知道错误信息。 则可以返回Result 并将错误网上传递。

 #![allow(unused)]
 fn main() {
 use std::fs::File;
 use std::io::{self, Read};
 
 fn read_username_from_file() -> Result<String, io::Error> {
     let username_file_result = File::open("hello.txt");
 
     let mut username_file = match username_file_result {
         Ok(file) => file,
         Err(e) => return Err(e),
     };
 
     let mut username = String::new();
 
     match username_file.read_to_string(&mut username) {
         Ok(_) => Ok(username),
         Err(e) => Err(e),
     }
 }
 }
 

理解:

  • 当成功开启,并读取后,返回Ok(username
  • 当失败, 返回Err(e)

优化写法, 使用?运算符

 #![allow(unused)]
 fn main() {
 use std::fs::File;
 use std::io::{self, Read};
 
 fn read_username_from_file() -> Result<String, io::Error> {
     let mut username_file = File::open("hello.txt")?;
     let mut username = String::new();
     username_file.read_to_string(&mut username)?;
     Ok(username)
 }
 }
 

? 运算符即当Err的时候, 直接返回Err(e). (需要对应的函数是Result的返回类型)

该运算符可是使用链式方法调用进一步缩短代码

继续优化:

 #![allow(unused)]
 fn main() {
 use std::fs::File;
 use std::io::{self, Read};
 
 fn read_username_from_file() -> Result<String, io::Error> {
     let mut username = String::new();
 
     File::open("hello.txt")?.read_to_string(&mut username)?;
 
     Ok(username)
 }
 }

文件中读取一个字符串是较为常见的操作, 所以Rust提供了fs::read_to_string的函数。

以上代码继续优化:

 #![allow(unused)]
 fn main() {
 use std::fs;
 use std::io;
 
 fn read_username_from_file() -> Result<String, io::Error> {
     fs::read_to_string("hello.txt")
 }
 }
 

注意: fs::read_to_string 的函数,它会打开文件、新建一个 String、读取文件的内容,并将内容放入 String,接着返回它。当然,这样做就没有展示所有这些错误处理的机会了,所以我们最初就选择了艰苦的道路。

要不要使用painc

一个函数应该使用painc还是返回Result? 这个应该看情况决定。

使用painc 则不管什么场景都直接失败, 无法恢复。

返回Result则让调用方来决定, 而非函数自行决定。

错误处理指导原则

在当有可能会导致有害状态的情况下建议使用 panic! —— 在这里,有害状态是指当一些假设、保证、协议或不可变性被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值 —— 外加如下几种情况:

  • 有害状态是非预期的行为,与偶尔会发生的行为相对,比如用户输入了错误格式的数据。
  • 在此之后代码的运行依赖于不处于这种有害状态,而不是在每一步都检查是否有问题。
  • 没有可行的手段来将有害状态信息编码进所使用的类型中的情况。

如果别人调用你的代码并传递了一个没有意义的值,尽最大可能返回一个错误,如此库的用户就可以决定在这种情况下该如何处理。然而在继续执行代码是不安全或有害的情况下,最好的选择可能是调用 panic! 并警告库的用户他们的代码中有 bug,这样他们就会在开发时进行修复。类似的,如果你正在调用不受你控制的外部代码,并且它返回了一个你无法修复的无效状态,那么 panic! 往往是合适的。

然而当错误预期会出现时,返回 Result 仍要比调用 panic! 更为合适。这样的例子包括解析器接收到格式错误的数据,或者 HTTP 请求返回了一个表明触发了限流的状态。在这些例子中,应该通过返回 Result 来表明失败预期是可能的,这样将有害状态向上传播,调用者就可以决定该如何处理这个问题。使用 panic! 来处理这些情况就不是最好的选择。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表