错误处理-Rust

分类

Rust将错误分为可处理的和不可处理的

对于一个可恢复的错误,比如文件未找到的错误,我们很可能只想向用户报告问题并重试操作。

对于一个不可恢复的错误,比如试图访问一个超过数组末端的位置,因此我们要立即停止程序。

Rust 没有异常。相反,它有 Result<T, E> 类型,用于处理可恢复的错误,还有 panic! 宏,在程序遇到不可恢

复的错误时停止执行。

panic!

主动调用panic宏

image-20241218164320910

被动的出现一类panic

image-20241218164649125

这种程序崩溃的情况往往是我们不希望出现的,在非必要的时候不要直接结束程序

Result

枚举

1
2
3
4
enum Result<T, E> {
Ok(T),
Err(E),
}

T 和 E 是泛型类型参数;第十章会详细介绍泛型。现在你需要知道的就是 T 代表成功时返回的Ok 成员中的数据的类型,而 E 代表失败时返回的 Err 成员中的错误的类型

image-20241218165104211

我们打开一个文件的时候有可能会遇到使用者输入的文件名错误或者文件不存在等情况,这个时候我们往往希望用户可以重新操作确认一下自己的输入是否有误

当 File::open 成功时,greeting_file_result 变量将会是一个包含文件句柄的 Ok 实例。

当 File::open 失败时,greeting_file_result 变量将会是一个包含了更多关于发生了何种错误的信息的 Err 。

这个时候我们可以使用match来操作两种可能的情况

1
2
3
4
5
6
7
8
9
10
11
12
use std::fs::File;
fn main() {

let greeting_file_result = File::open("hello.txt");


let greeting_file= match greeting_file_result {
Ok(v)=>v,
Err(e)=>panic!("error file name or file not exits!"),
};

}

Result 枚举和其成员也被导入到了 prelude 中,所以就不需要在

match 分支中的 Ok 和 Err 之前指定 Result::

这个时候程序发现如果不存在文件会直接panic,我们希望程序更加优雅,这时候可以在文件不存在的情况下创建文件而不是直接panic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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() {//error.kind()获取报错的具体类型

ErrorKind::NotFound => match File::create("hello.txt") {//如果是文件未找到那么执行后面的操作创建新文件,
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {e:?}"),//创建新文件有可能也出错,比如权限不够,又需要一个match匹配
},
other_error => {panic!("Problem opening the file: {other_error:?}"); }
},
};
}

这里有好多 match!match 确实很强大,不过也非常的原始。

闭包(closure)它通常会和定义在 Result<T, E> 中的很多方法一起使用。在处理代码中的Result<T, E> 值时,相比于使用 match ,使用这些方法会更加简洁。

如下代码与上面的例子效果一致

1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
});
}

panic的简写

unwrap和expect

image-20241218171027065

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

expect 与 unwrap 的使用方式一样:返回文件句柄或调用 panic! 宏。expect 在调用 panic! 时使用的错误信息将是我们传递给 expect 的参数,而不像 unwrap 那样使用默认的 panic! 信息。

传播错误

当编写一个其实先会调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还

可以选择让调用者知道这个错误并决定该如何处理。这被称为 传播(propagating)错误,这

样能更好的控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑

来决定应该如何处理错误。

编写一个从文件内容读取数据写入变量的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
use std::{fs::File, io::{self, ErrorKind, Read}};

fn main() {

let name=read_name_from_file();
match name {
Ok(s)=>println!("{}",s),
Err(e)=>eprintln!("{}",e),
}


}


fn read_name_from_file()->Result<String,io::Error>{
let username_file=File::open("hello.txt");

let mut user_file=match username_file {
Ok(file)=>file,
Err(e)=>return Err(e),
};

let mut username=String::new();

match user_file.read_to_string(&mut username) {
Ok(_)=>Ok(username),
Err(e)=>Err(e),

}


}

我们可以简化一下match,如下代码效果一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
use std::{fs::File, io::{self, ErrorKind, Read}};

fn main() {

let name=read_name_from_file();
match name {
Ok(s)=>println!("{}",s),
Err(e)=>eprintln!("{}",e),
}


}


fn read_name_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)

}

自定义类型验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fn main() {

let g1=Guess::new(130);

println!("{:?}",g1.value);

}


#[derive(Debug)]
pub struct Guess {
value:i32,
}

impl Guess {
pub fn new(value:i32)->Guess{
if value<1 ||value>100{
panic!("请输入1-100之间的数字!!!!!!");
}
Guess { value }
}

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

}

如上代码我们在创建Guess类型的时候需要通过new函数的检查,是否为1-100的数字,不符合规范的数字会panic,value函数相当于java面向对象的get,通过value获取结构体本身的值,防止直接使用.

修改数据而绕过new的检查

image-20250410160020708