《Rust权威指南》 第9章 错误处理

在Rust中,错误被分成两大类:可恢复错误与不可恢复错误。Rust没有异常机制,而是为这两种错误分别提供了一些方法

  • 可恢复错误:需要将错误报告给用户并再次进行尝试。使用Result<T,E>
  • 不可恢复错误:bug。使用panic!

不可恢复错误与panic!

程序会在panic!宏执行时打印出一段错误提示信息,展开并清理当前调用栈,退出程序

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

运行程序,会显示错误信息

PS E:\Rust\Demo\panic> cargo run
   Compiling panic v0.1.0 (E:\Rust\Demo\panic)
    Finished dev [unoptimized + debuginfo] target(s) in 1.68s
     Running `target\debug\panic.exe`
thread 'main' panicked at 'crash!', src\main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

关注最后两行,第一行显式我们向panic!提供的信息,并指出源代码panic发生的位置

使用panic!产生的回溯信息

在上面错误信息的最后一行"note: run with RUST_BACKTRACE=1 environment variable to display a backtrace"
意思是我们通过修改环境变量RUST_BACKTRACE=1,来得到回溯信息
回溯中包含了到达错误点的所有调用函数列表

例如我们的程序是

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

不启用回溯的错误信息:

PS E:\Rust\Demo\panic> cargo run
   Compiling panic v0.1.0 (E:\Rust\Demo\panic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
     Running `target\debug\panic.exe`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', src\main.rs:3:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

在run之前加入RUST_BACKTRACE=1启用回溯后的错误信息
(注意,用windows系统需要$env:RUST_BACKTRACE=1 ; cargo run

PS E:\Rust\Demo\panic> $env:RUST_BACKTRACE=1 ; cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target\debug\panic.exe`
thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 100', src\main.rs:3:5
stack backtrace:
   0: std::panicking::begin_panic_handler
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library\std\src\panicking.rs:579
   1: core::panicking::panic_fmt
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library\core\src\panicking.rs:64
   2: core::panicking::panic_bounds_check
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc/library\core\src\panicking.rs:159
   3: core::slice::index::impl$2::index<i32>
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc\library\core\src\slice\index.rs:260
   4: core::slice::index::impl$0::index
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc\library\core\src\slice\index.rs:18
   5: alloc::vec::impl$13::index<i32,usize,alloc::alloc::Global>
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc\library\alloc\src\vec\mod.rs:2703
   6: panic::main
             at .\src\main.rs:3
   7: core::ops::function::FnOnce::call_once<void (*)(),tuple$<> >
             at /rustc/84c898d65adf2f39a5a98507f1fe0ce10a2b8dbc\library\core\src\ops\function.rs:250
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

可恢复错误与Result

Result是一个枚举类型

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

执行成功时,返回Ok变体;执行失败时,返回Err变体
Option<T>一样,使用match进行处理

use std::fs::File;

fn main() {
    let f = File::open("hello.tex");

    let f = match f{
        Ok(file) => file,
        Err(error) => {
            panic!("{:?}",error);
        },
    }
}

在这个特定的例子中,泛型参数Tstd::fs::File是文件的句柄;Estd::io::Error

匹配不同的错误

使用嵌套match配合方法error.kind()来匹配不同的错误

use std::fs::File;

fn main() {
    let f = File::open("hello.tex");

    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!("creating file but a problem:{:?}",e),
            },
            other_error => panic!("opening the file with problem:{:?}",other_error),
        },
    };
}

如果错误类型是枚举量io::ErrorKind::NotFound,就尝试创建一个新文件。注意创建时又有一个match来处理新建文件可能出现的问题

失败时触发panic的快捷方式:unwrap和expect

这两个方法相当于打包了我们上面的match逻辑
如果Result是Ok变体,这两个方法就会返回Ok变体里附加的值;如果是Err变体,这两个方法会触发panic。不同点在于,触发panic时,unwrap只会打印一段简短的默认信息;而expect(…)则允许我们自定义一段附加信息

fn main() {
    let f = File::open("hello.tex").unwrap();
}
fn main() {
    let f = File::open("hello.tex").expect("Failed to open hello.tex");
}

传播错误

像是在其它编程语言我们不停的向上层抛出异常一样,在Rust中向函数的调用者传播错误也是一种常见的做法
最简单的,我们将函数设计为返回Result类型。并将Result类型设计为第一个参数类型是函数执行成功时应该返回的类型;第二个参数类型是函数在运行时可能出现的错误类型

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

fn read_from_file() -> Result<String,io:Error> {
    let f = File::open("hello.txt");
    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    }
    let mut s = String::new();
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

传播错误的快捷方式:?运算符

将?放于Result值之后,如果值是Ok,包含在Ok中的值就会作为表达式的结果返回并继续执行函数;如果是Err,那么这个值就会作为整个函数的结果返回
可以使用?运算符改编上一个程序,并保持功能基本上不变
唯一与match版本的区别是:被?运算符接收的错误值会隐式的被from函数处理,该函数总是尝试将?运算符传入的错误类型转化为函数返回值的错误类型

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

fn read_from_file() -> Result<String,io:Error> {
    let f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

甚至采用链式调用来进一步缩短程序

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

fn read_from_file() -> Result<String,io:Error> {
    let mut s = String::new();
    File::open("hello.txt")?.read_to_string(&mut s)?;
    Ok(s)
}
?运算符只能用于返回Result的函数

比如我们的一般main函数返回值是(),自然不能使用?运算符
那有没有方法在main函数中使用?
有,那就是变main函数的返回类型

use std::error::Error;
use std::fs::File;

fn main() -> Result<(),Box<dyn Error>> {
	let f = File::open("hello.txt")?;
	Ok(());
}

要不要使用panic!

示例、原型和测试

使用panic

  • 编写示例时,为了健壮性添加错误处理代码往往会减弱示例的可读性。此时应该直接使用unwrap之类可能导致panic的方法作为某种占位符,用来标明这些是需要程序进一步处理的错误
  • 在原型中,不确定具体错误的具体处理方式,一般也是使用unwrap和expect来占位
  • 在测试中,快速判断测试是否失败

当你比编译器拥有更多信息时

当你拥有某些逻辑可以确保Result一定是一个Ok值时,直接使用unwrap来简化代码
比如

use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();

错误处理的指导原则

损坏状态:

  • 设计中的一些假设、保证、约定或不可变性出现了被打破的情形
  • 损坏状态不包括预期中偶尔会发生的事情
  • 代码无法在出现损坏状态后继续正常运行
  • 没有合适方法将“处于损坏状态”这一信息,编码至我们所使用的类型中

几条原则:

  1. 出现损坏时,最好是触发panic来终止程序
  2. 错误是可预期的,使用Result
  3. 代码基于某些值进行操作时,首先验证值的有效性,并在无效时触发panic

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

Rust的类型系统会自动的做有效性验证,比如你无法给u32赋值一个负数
当我们创建自己的新类型时,如何保证有效性呢?
一个做法是 setter-getter:setter为值进行设置,并检查值是否有效;getter来进行取值;注意值要设置为私有的,保证不能有除setter之外的手段来修改值
一个例子:值必须在1~100的整数类型

pub struct Num {
    value:i32,
}

impl Num {
    pub fn new(value:i32) -> Num {
        if value < 1 || value > 100 {
            panic!("illigal value {} for Num!",value);
        }
        Num {
            value
        }
    }

    pub fn value(&self) -> i32 {
        self.value;
    }
}

其中setter是关联函数new,该函数检查值是否合法,不合法时触发panic
getter是方法value
字段value设置为私有

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值