Rust学习(八):异常处理和宏系统
1、异常处理:
异常处理是任何编程语言都会遇到的现象,Rust并没有像其他变成语言一样提供了try catch这样的异常处理方法,而是提供了一种独特的异常处理机制。这里需要指明的是作者在书中将Rust中的失败、错误、异常等统称为异常,Rust中的异常有4种:Option, Result, Panic和Abort。
Option用于应对可能的失败情况,Rust使用Some和None来表示是否失败,以获取值为例:如果没有获取到值,就会返回None作为返回值,这时不需要报错,只需要依据情况处理即可。失败和错误不同,程序执行失败意味着代码设计是符合逻辑的,不会导致程序出问题(比如:需要浮点数类型,实际上是字符类型),Option的定义如下:
enum Option<T> {
Some(T),
None,
}
Result用于应对可恢复的错误,和Option十分相似,Option可以看成是Result<T, ()>,下面是Result的定义:
enum Result<T, E> {
Ok(T),
Err(E),
}
打开不存在或者没有权限的文字,或者将非数字字符串转换成数字,都会得到Err(E):
use std::fs::File;
use std::io::ErrorKind;
let f = File::open("kw.txt");
let f = match f {
Ok(file) => file,
Err(err) => match err.kind()
ErrorKind::NotFound => match File::create("kw.txt") {
Ok(fc) => fc,
Err(e) => panic("Error while creating file"),
}
ErrorKind::PermissionDenied => panic("No permission!"),
other => panic("Error while openning file"),
}
这里对可能遇到的错误逐一进行了处理,实在无法处理才会使用Panic,这一点一定要紧记:测试时使用Panic,实际上线时,最好使用错误处理机制!Panic是在遇到不可回复的错误时,清理内存,如果使用操作系统来清理内存,可以使用Abort。
Rust提供了unwrap或expect,来简化错误处理的流程:
use::std::fd::File;
use::std::io;
use::std::io::Read;
fn main() {
let f = File::open("kw.txt").unwrap();
let f = File::open("kw.txt").expect("Open file failed!");
}
如果遇到错误只想上抛,不想处理,可以使用“?”。此时返回类型是Result,成功返回String,错误返回:io::err:
fn main() {
let s = read_from_file();
match s {
Err(e) => println!("{e}");
Ok(s) => println!("{s}");
}
}
fn read_from_file() -> Result<String, io::Error> {
let f = File::open("kw.txt")?; //报错时直接抛出
let mut s = String::new();
f.read_to_string(&mut s);
Ok(s)
}
2、宏系统:
Rust 中并不存在内置库函数,一切都需要自己定义。但是 Rust 实现了一套高效的宏,包括声明宏、过程宏,利用宏能完成非常多的任务。C 语言中的宏是一种简单的替换机制,很难对数据做处理;Rust 中的宏则强大得多,得到了广泛应用。比如,使用 derive 宏可以为结构体添加新的功能,常用的 println!、vec!、panic!等也是宏。
Rust 中的宏有两大类:一类是使用 macro rules! 声明的声明宏;另一类是过程宏,过程宏又分为三小类——derive 宏、类属性宏和类函数宏。因为前面使用过声明宏和 derive 宏,所以下面只打算介绍声明宏和 derive 宏。其他的宏,请通过查阅相关资料来学习。
声明宏的格式:macro_name!()、macro_name![]、macro_name!{}。首先是宏名,然后是感叹号,最后是()、[]或 {}。这些括号都可用于声明宏,但不同用途的声明宏使用的括号是不同的,比如是 vec![] 而不是 vec!(),带“()”的更像是函数,这也是 println!() 使用“()”的原因。不同的括号只是为了满足意义和形式上的统一,实际使用时任何一种都可以。
macro_rules! marco_name {
($matcher) => {
$code_body;
return_value
};
}
在上面的宏定义中, matcher 用于标记一些语法元素,比如空格、标识符、字面值关键字、符号、模式、正则表达式,注意前面的符号 $ 用于捕获值。$code_body 将利用 Smatcher 中的值来进行处理,最后返回 return_value ,当然也可能不返回。比如,要计算二叉树中父节点 p 的左、右子节点,可以使用如下宏,节点 p 的左、右子节点的下标应该是 2p 和 2 p + 1 2p + 1 2p+1 :
macro_rules! left_child {
($parent:ident) => {
$parent << 1
};
}
macro_rules! right_child {
($parent:ident) => {
($parent << 1) + 1
};
}
过程宏更像是函数或一种过程。过程宏接收代码作为输入,然后在这些代码上进行操作并产生另一些代码作为输出。derive过程宏又称派生宏,形式如下:
#[derive(Clone)]
struct Student;
derive 过程宏通过这种标记形式为 Studen 实现了 Clone 里定义的方法,这样 Student 就可以直接通过调用 clone() 方法来实现复制。”这种形式其实就是 impl Clone for Student 的简易写法。derive 过程宏里也可以同时放多个 trait,这样就可以实现各种功能了:
#[derive(Dedug, Clone, Copy)]
struct Student;
宏属于非常复杂的系统,一般在必要且能简化代码的情况下使用宏。