Rust(九)-错误处理

311 阅读4分钟

panic! 不可恢复的错误

  • Rust错误处理概述
    • Rust的可靠性:错误处理
      • 大部分情况下: 在编译时提示错误, 并处理
    • 错误的分类:
      • 可恢复
        • 例如文件未找到,可再次尝试
      • 不可恢复
        • bug,例如访问的索引超出范围
    • rust 没有类似异常的机制
      • 可恢复错误:Result<T,E>
      • 不可恢复:panic!宏
    • 不可恢复的错误与panic!
      • 当panic!宏执行:
        • 你的程序会打印一个错误信息
        • 展开(unwind)、清理调用栈(stack)
        • 退出程序
      • 默认情况下,当panic发生:
        • 程序展开调用栈(工作量大)
          • Rust沿着调用栈往回走
          • 清理每个遇到的函数的数据
        • 或立即中止调用栈:
          • 不进行清理,直接停用程序
          • 内存需要os进行清理
        • 想让二进制文件更小,把设置从“展开”改为“中止”:
          • 在cargo.toml中适当的profile部分设置
            • panic = 'abort'

image.png

  • 使用panic!产生的回溯信息
    • panic!可能出现在:
      • 我们写的代码中
      • 我们所依赖的代码中
    • 可通过调用panic!的函数的回溯信息来定位引起问题的代码
    • 通过设置环境变量RUST_BACKTRACE可得到回溯信息
    • 为了获取带有调试信息的回溯,必须启用调试符号(不带 --release)
  • result枚举 +
    enum result<T,E>{
        Ok(T),
        Err(E)
    }
    
    • T: 操作成功情况下,Ok变体里返回的数据的类型
    • E: 操作失败情况下,Err变体里返回的错误的类型
  • 处理result的一种方式: match表达式
use std::fs::File;
fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => panic!("Problem opening the file: {:?}", error),
    };
}
use std::fs::File;
use std::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!("Problem creating the file: {:?}", e),
            },
            other_error => {
                panic!("Problem opening the file: {:?}", other_error)
            }
        },
    };
}

  • unwrap
    use std::fs::File;
    
      fn main() {
          let f = File::open("hello.txt").unwrap();
      }
    
    
  • Expect
    • expect:和unwrap类似,但可指定错误信息
    use std::fs::File;
    
      fn main() {
          let f = File::open("hello.txt").expect("Failed to open hello.txt");
      }
    
    
  • 传播错误
  • 在函数出处理错误
  • 将错误返回给调用者
  • ?操作符
    • ? 运算符:传播错误的一种快捷方式
    • 如果result是ok: ok中的值就是表达式的结果,然后继续执行程序
    • 如果result是err: err就是整个函数的返回值,就像使用了return
use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}
  • ? 与 from 函数
    • Trait std::convert::From 上的from的函数:
      • 用于错误之间的转换
    • 被 ?所应用的错误,会隐式的被from函数处理
    • 当 ? 调用from函数时:
      • 它所接收的错误类型会被转化为当前函数返回类型所定义的错误类型
    • 用于:针对不同错误原因,返回同一种错误类型
  • ?运算符只能用于返回result的函数
  • ?运算符与main函数
    • main函数返回类型是:()
    • main函数的返回类型也可是:Result<T,E>
    use std::fs::File;
      fn main() {
          let f = File::open("hello.txt").expect("Failed to open hello.txt");
      }
    
  • 什么时候应该用panic!
    • 总体原则
      • 在定义一个可能失败的函数时,优先考虑返回result
      • 否则就panic!
    • 可以使用Panic
      • 演示某些代码(unwrap)
      • 原型代码(unwrap、expect)
      • 测试 (unwrap、expect)
    • 有时你比编译器掌握更多的信息
      • 你可以确定result就是ok: unwrap
    • 错误处理的指导性建议
      • 当代码最终可能处于损坏状态时,最好使用panic
      • 损坏状态(bad state):某些假设、保证、约定或不可变性被打破
        • 例如非法的值、空缺的值、矛盾的值被传入代码
        • 以及下列中的一条
          • 这种损坏状态并不是预期能够偶尔发生的事情
          • 在此之后,如果您的代码出现这种损坏就无法运行
          • 在您使用的类型中没有一个好的方法来处理这些信息
      • 场景建议
        • 调用你的代码,传入无意义的值,最好使用Panic
        • 调用外部代码,出现问题,你无法修复
        • 如果失败是可预期的:Result
        • 当你的代码对值进行操作,首先应该验证这些值:panic.
      • 为验证创建自定义类型
        • 创建新的类型,把验证类型放在构造实例的函数里。
        
          #![allow(unused)]
          fn main() {
          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
              }
          }
          }
        
        
      • getter:返回字段数据
        • 字段是私有的:外部无法直接对这段赋值