自学内容网 自学内容网

Rust学习(八):异常处理和宏编程:

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;

宏属于非常复杂的系统,一般在必要且能简化代码的情况下使用宏。


原文地址:https://blog.csdn.net/2301_77286822/article/details/143983033

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!