Rust的可靠性

错误处理

  • 通常情况下,Rust会在编译时提示错误,进行处理

    Rust错误分类

  • 可恢复

    • 例如文件没找到之类的,可再次尝试的诸如此类的问题
    • Result
  • 不可恢复
    • 就是通常我们说的bug,代码逻辑出现的问题。例如访问的索引超出边界
    • panic! 宏

      不可恢复错误

      panic!默认执行步骤

      当执行panic! 宏的时候会执行以下步骤:
  1. 程序会打印一个错误信息
  2. 展开(unwind)、清理调用栈
  3. 退出程序

    这其中在执行第二步的时候会特别浪费性能,我们可以选择中止(abort)调用栈(默认是开启的展开清理调用栈)。

程序展开调用栈(工作量大)

  • rust沿着调用栈往回走
  • 清理每个遇到的函数中的数据

    设置panic为abort模式

    当我们选择中止调用栈时,会执行一下步骤
  1. 不进行清理,直接停止程序
  2. 内存需要OS进行清理

设置方法:

  • 在Cargo.toml中将profile部分进行设置:
    • panic = ‘abort’
      1. // Cargo.toml文件
      2. [profile.release]
      3. panic = 'abort'

可恢复错误与Result

Result 就是一个枚举

enum Result { 
    Ok(T), 
    Err(E), 
}
  • T:操作成功情况下,Ok变体里的数据类型
  • E:操作失败情况下,Err变体里的数据类型

    利用match处理Result

    ```rust use std::fs::File;

fn main() { let f = File::open(“hello.txt”); // 利用match来匹配Result对应的类型做处理 let f = match f { Ok(file) => file, Err(e) => { panic!(“错误信息为:{:?}”, e); } }; }

对于上面这种错误,我们可以继续细分错误类型,来进行不同的处理 <br />比如我们可以在错误类型为NotFound的时候,去创建对应的文件 <br />判断错误类型,标准库里也提供了对应的方法,就是std::io::ErrorKind
```rust
use std::{fs::File, io::ErrorKind}; 


fn main() { 
    let f = File::open("README.md"); 
    let f = match f { 
        Ok(file) => file, 
        Err(e) => match e.kind() { 
            // 错误类型如果是NotFound,就创建文件 
            ErrorKind::NotFound => match File::create("hello.txt") { 
                // 匹配创建文件时的Result 
                Ok(fc) => fc, 
                Err(err) => panic!("文件创建失败{:?}", err), 
            }, 
            other_error => panic!("错误信息为:{:?}", other_error), 
        }, 
    }; 
    print!("{:?}", f) 
}

可以看出来其实我们上面利用match方法进行的错误处理,代码量是很大的,写起来很繁琐。
所以Rust提供了一些简单的方法让我们来完成类似的工作

利用unwrap替代match

use std::fs::File; 


fn main() { 
    let f = File::open("hello.txt"); 
    // 利用match来匹配Result对应的类型做处理 
    let f = match f { 
        Ok(file) => file, 
        Err(e) => { 
            panic!("错误信息为:{:?}", e); 
        } 
    }; 
} 

//利用unwrap改造 
fn main(){ 
    let f = File::open("hello.txt").unwrap(); 
 }

从上面这个例子其实就可以看出来,unwrap 的作用:

  1. 如果Result结果是Ok,返回Ok里的值
  2. 如果Result结果是Err,调用painc!宏

当然unwrap 也有缺点,就是不能自定义错误信息。所以expect 就横空出世

//利用expect改造 
fn main(){ 
    let f = File::open("hello.txt").expect("自定义错误信息"); 
 }

传播错误和?

当你的程序中出现错误的时候,不仅可以处理错误,还可以将错误传播给调用者,让调用者知道该错误的发生

use std::{ 
    fs::File, 
    io::{self, Read}, 
}; 


fn main() { 
    let f = read_username_from_file("hello.txt".to_string()); 
    print!("{:?}", f) 
} 

// 该函数会将错误返回给调用者 
fn read_username_from_file(path: String) -> Result<String, io::Error> { 
    // 首先读取文件 
    let f = File::open(path); 
    // 匹配Result 
    let mut f = match f { 
        Ok(file) => file, 
        Err(error) => return Err(error),// 这里返回错误 
    }; 
    // 创建需要返回的String 
    let mut s = String::new(); 
    // 读取文件内容(这里也是将匹配结果返回) 
    match f.read_to_string(&mut s) { 
        Ok(_) => Ok(s), 
        Err(err) => Err(err), 
    } 
}
  • 如果发生错误,例如文件没找到,就会打印错误

🥭错误处理 - 图1

  • 如果没有发生错误,就会打印文件内容

🥭错误处理 - 图2
但是从上面的例子可以看出,传播错误的代码也很繁琐,所以rust又提供了一个语法糖? 来帮助我们完成传播错误这一操作。

use std::{ 
    fs::File, 
    io::{self, Read}, 
}; 


fn main() { 
    let f = read_username_from_file("hello.txt".to_string()); 
    print!("{:?}", f) 
} 


fn read_username_from_file(path: String) -> Result<String, io::Error> { 
    // 首先读取文件,利用?来传播错误 
    let mut f = File::open(path)?; 
    // 创建需要返回的String 
    let mut s = String::new(); 
    f.read_to_string(&mut s)?; 
    Ok(s) 
}

从上面的代码实例可以看出,? 的作用其实很简单:

  1. 如果Result类型是Ok的话,就会返回Ok的类型值(这里的返回其实相当于match的返回,函数里的代码还会正常执行
  2. 如果Result类型是Err的话,就会把Err(e) 作为整个函数的返回值进行返回(这里的返回就是函数执行到这里就结束了,不会往下执行了,整个函数的结果已经产生了)
    let mut f = File::open(path)?; 
    // 两段代码的效果是一样的 
    let mut f = match f { 
         Ok(file) => file, 
         Err(error) => return Err(error),// 这里返回错误 
    };
    
    ? 与from函数:
  • 被?所应用的错误,会隐式的调用from函数处理
    • 当?调用from函数时:他所接收的错误类型就会转换为当前函数返回类型定义的错误类型

就相当于我们将错误类型A转换成了Result,转换成了io::Error的错误类型

  • 应用场景:针对不同错误原因,返回另一种错误类型
    • 前提条件:只要每个错误类型实现了转换为所返回的错误类型的from函数

      总结

      🥭错误处理 - 图3