错误处理(9)

错误处理

    • 1.用panic!处理不可恢复的错误
      • 1.1对应panic时的栈展开或终止
      • 1.2使用panic!的backtrace
        • 1.尝试访问超越vector结尾的元素,这会造成panic!
        • 2.当设置RUST_BACKTRACE环境变量时panic!调用所生成的backtrace信息
    • 2.用Result处理可恢复的错误
      • 1.使用match表达式处理可能会返回的Result成员
      • 2.匹配不同的错误
        • 1.使用不同的方式处理不同类型的错误
        • 2.不同于使用match和Result
      • 2.失败时panic的简写:unwrap 和 expect
        • 1.unwrap
        • 2.expect
      • 3.传播错误
        • 1.函数使用match将错误返回给代码调用者
      • 4.传播错误的简写: ?运算符
      • 5.使用fs::read_to_string而不是打开后读取文件
      • 6.哪些情况可以使用?运算符
        • 6.1尝试在返回()的main函数中使用?的代码不能编译
        • 6.2在Option值上使用? 运算符
        • 6.3修改main返回Result<(),E>允许对Result值使用?运算符
    • 3.要不要panic!
      • 3.1错误处理指导原则
      • 3.2创建自定义类型进行有效性验证
        • 1.一个Guess类型,它只在位于1和100之间时才会继续

  • Rust将错误分为两个大类: 可恢复的不可恢复的

1.用panic!处理不可恢复的错误

  • 在实践中有两种方法造成panic:
    • 执行会造成代码panic的操作
    • 显式调用panic!宏
  • 通常情况下这些panic会打印一个错误信息,展开并清理数据,然后退出

1.1对应panic时的栈展开或终止

  • 当出现panic时,程序默认会开始展开,这意味着Rust会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作
  • 另一种选择是直接终止,这会不清理数据就退出程序
  • panic时通过在Cargo.toml的[profile]部分增加panic = 'abort',可以由展开切换为终止

1.2使用panic!的backtrace

1.尝试访问超越vector结尾的元素,这会造成panic!
fn main() {
    let v = vec![1,2,3];
    v[99];
}

  • C语言中,尝试读取数据结构之后的值是未定义行为;会得到任何对应数据结构中这个元素的内存位置的值,甚至是这些内存并不属于这个数据结构的情况,被称为缓冲区溢出,并可能导致安全漏洞
  • 为了保护程序远离这类漏洞,尝试读取一个索引不存在的元素,Rust会停止执行并拒绝继续
PS C:\Tools\devTools\vscode\code\rust\world_hello> cargo run
   Compiling world_hello v0.1.0 (C:\Tools\devTools\vscode\code\rust\world_hello)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target\debug\world_hello.exe`
thread 'main' panicked at src\main.rs:3:6:
index out of bounds: the len is 3 but the index is 99
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\world_hello.exe` (exit code: 101)
2.当设置RUST_BACKTRACE环境变量时panic!调用所生成的backtrace信息
>$env:RUST_BACKTRACE=1; cargo run 
  • backtrace是一个指向到目前位置所有被调用的函数列表

2.用Result处理可恢复的错误

1.使用match表达式处理可能会返回的Result成员

use std::fs::File;
fn main() {
    let greeting_file_result = File::open("../Cargo.lock");
    let greeting_file = match greeting_file_result{
        Ok(file) =>file,
        Err(error)=>panic!("Problem opening the file: {:?}",error),
    };
}
enum Result<T,E>{
    Ok(T),
    Err(E),
}
  • 输出
thread 'main' panicked at src\main.rs:6:21:
Problem opening the file: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library\std\src\panicking.rs:597
   1: core::panicking::panic_fmt
             at /rustc/79e9716c980570bfd1f666e3b16ac583f0168962/library\core\src\panicking.rs:72

2.匹配不同的错误

1.使用不同的方式处理不同类型的错误
use std::fs::File;
use std::io::ErrorKind;
fn main() {
    let greeting_file_result = File::open("../Cargo.lock");
    let greeting_file = match greeting_file_result{
        Ok(file) =>file,
        Err(error)=> match error.kind() {
            ErrorKind::NotFound =>match File::create("Cargo.lock") {
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {:?}",e),
                
            },
            other_error =>{
                panic!("Problem opening the file: {:?}",other_error);
            }
        }
    };
}
enum Result<T,E>{
    Ok(T),
    Err(E),
}

2.不同于使用match和Result
use std::fs::File;
use std::io::ErrorKind;
fn main() {
    let greeting_file_result = File::open("../Cargo.lock").unwrap_or_else(|error|{
        if error.kind() == ErrorKind::NotFound{
            File::create("../test.txt").unwrap_or_else(|error|{
                panic!("Problem creating the file: {:?}",error);
            })
        }else{
            panic!("Problem opening the file: {:?}",error);
        }
    });
}

2.失败时panic的简写:unwrap 和 expect

1.unwrap
  • match能够胜任它的工作,不过它可能有点冗长并且不总是能很好的表明其意图
  • Result类型定义了很多辅助方法来处理各种情况
    • unwrap,Result值是成员Ok,unwrap会返回Ok中的值
    • Result是成员Err,unwrap会调用panic!
use std::fs::File;

fn main() {
    let greeting_file_result = File::open("../Cargo.lock").unwrap();
}

2.expect
  • 使用expect而不是unwrap并提供了一个好的错误信息可以表明你的意图方便追踪panic的根源
use std::fs::File;

fn main() {
    let greeting_file_result = File::open("../Cargo.lock").expect("Cargo.lock should be included in this project");
}
  • 生产级别的代码中,大部分选择expect

3.传播错误

  • 编写一个其实先会调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理,这被称为传播错误
1.函数使用match将错误返回给代码调用者
use std::fs::File;
use std::io::{self,Read};
fn main() {
    read_username_from_file();
}
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),
    }
}

4.传播错误的简写: ?运算符

use std::fs::File;
use std::io::{self,Read};
fn main() {
    read_username_from_file();
}
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)?;
    //链式编程  同上
    // File::open("hello.txt")?.read_to_string(&mut username)?;
    Ok(username)
}

5.使用fs::read_to_string而不是打开后读取文件

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

6.哪些情况可以使用?运算符

  • ?运算符只能被用于返回值与?作用的值相兼容的函数
6.1尝试在返回()的main函数中使用?的代码不能编译
  • 这个错误指出在返回Result或者其它实现了FromResidual的类型的函数中使用?运算符
  • 错误信息也提到了?也可用于Option< T>值
use std::fs::File;

fn main() {
	// 不能编译
    // let greeting_file = File::open("hello.txt")?;
}
6.2在Option值上使用? 运算符
fn last_char_of_first_line(text: &str) -> Option<char>{
	text().lines().next()?.chars().last()

}
6.3修改main返回Result<(),E>允许对Result值使用?运算符
  • main函数也可以返回任何实现了 std::process::Termination trait的类型,它包含了一个返回ExitCode的report函数
  • main函数返回Result<(),E>,如果main返回Ok(())可执行程序以0值退出;如果main返回Err值则会以非零值退出
use std::fs::File;
use std::error::Error;
fn main() -> Result<(),Box<dyn Error>> {
    // Box类型是一个trait对象
    let greeting_file = File::open("hello.txt")?;
    Ok(())
}

3.要不要panic!

3.1错误处理指导原则

  • 在当有可能会导致有害状态的情况下建议使用panic!
  • 有害状态是指当一些假设保证协议不可变性被打破的状态
  • 其他情况
    • 有害状态是非预期的行为,与偶尔会发生的行为相对
    • 在此之后的代码允许依赖于不处于这种有害状态,而不是在每一步都检查是否有问题
    • 没有可行的手段来将有害状态信息编码进所使用的类型中的情况

3.2创建自定义类型进行有效性验证

loop{
	let guess: i32 = match guess.trim().parse(){
		Ok(num) => num,
		Err(_) => countine,
	};
	if guess<1 || guess >100 {
		println!("The secret number will be between 1 and 100");
		continue;
	}
	match guess.cmp(&secret_number){
		
	}
}

1.一个Guess类型,它只在位于1和100之间时才会继续
pub struct Guess{
	value: i32,
}
impl Guess{
	pub fn new(value: i32)->Guess{
		if vaule < 1 || value >100 {
			panic!("Guess value must be between 1 and 100 ,got {}",value);
		}
		Guess {value}
	}
	// 这类方法有时被称为getter,目的就是返回对应字段的数据。 value字段是私有的
	pub fn value(&self) -> i32{
		self.value
	}

}

你可能感兴趣的:(Rust,rust,后端)