入门Rust的固定套路:错误处理模式有三大类,帮你总结了

2023年 12月 20日 56.8k 0

最近在学习总结Rust的各种场景的语法模式,也就是Rust写代码的模式。

今天分享关于Rust的错误处理的三大类语法模式。

先列出一个大纲

第一类:有意不处理错误,忽略错误

  • unwrap()
  • .fn( )? 符号,代替rust早期版本中的try!宏

第二类:对错误做自定义信息提示

  • 使用expect()。

第三类:推荐!根据正确和错误情况分开处理,错误还可以进一步分流处理

  • match(包括 match处理 Result或  match处理Option, 或 使用map_err())
  • 使用if let Some(value)= fn() {} else {}
  • 使用特定的函数:and_then() 和 or_else()

我对Rust的错误处理的印象

Rust的错误处理方式比起Golang更灵活,可以针对错误和当下代码需要赋值前做错误判断、故意忽略异常、异常时打印错误并终止等的不同编码场景,选用不同的语法模式。

错误处理涉及到数据类型、错误处理的控制语法、相关的crate模块。

Rust错误处理涉及到的数据类型

错误处理的类型1:Result

Result属于Rust的核心crate提供的功能(core::result::Result)。Result 有2个特点:

首先,Result 属于泛型,所以T和E可以是任意类型。但常见的使用模式是以T存储正常情况的信息,E存储错误和异常情况的信息。

其次,Result是枚举类型,其内部实际只包含:

Ok()
Err()

这两个枚举,所以它的实例只会是Ok()或Err()之一。

Result的代码实现中就用的枚举类型的,代码如下:

/// Result的定义在 rust核心代码 src/rust/library/core/src/result.rs 代码文件中:
/// `Result` is a type that represents either success ([`Ok`]) or failure ([`Err`]).
///
/// See the [module documentation](self "module documentation") for details.
#[derive(Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
#[rustc_diagnostic_item = "Result"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result {
    /// Contains the success value
    #[lang = "Ok"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Ok(#[stable(feature = "rust1", since = "1.0.0")] T),

    /// Contains the error value
    #[lang = "Err"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Err(#[stable(feature = "rust1", since = "1.0.0")] E),
}

实际使用的一个例子:例如Result 是一个泛型类型,则此时的T类型为String,E类型为&str。它表示一个可能成功或失败的操作的结果,其中成功时的返回类型为 String,失败时的错误类型为 &str。

总结一下Result:  result 类型通常用于表示可能成功或失败的操作的结果。它使用 Result 类型来表示,其中 T 是成功时的返回类型,E 是失败时的错误类型。

错误处理的类型2:Option(),none>

Option也是枚举类型,其内部实际只包含两种值:Some()和None,这个可以从其代码中得到印证。Option的实现代码为:

/// 代码文件 rustlib/src/rust/library/core/src/option.rs 定义了Option
/// The `Option` type. See [the module level documentation](self "the module level documentation") for more.
#[derive(Copy, PartialOrd, Eq, Ord, Debug, Hash)]
#[rustc_diagnostic_item = "Option"]
#[lang = "Option"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Option {
    /// No value.
    #[lang = "None"]
    #[stable(feature = "rust1", since = "1.0.0")]
    None,
    /// Some value of type `T`.
    #[lang = "Some"]
    #[stable(feature = "rust1", since = "1.0.0")]
    Some(#[stable(feature = "rust1", since = "1.0.0")] T),
}

Result 和 Option 有什么区别?

首先,从名称上理解一下:

  • Result 中文意思是结果。一般表示成功或失败,所以它的枚举值会是Ok()和Err();
  • Option,中文意思是选项。那么它的侧重点不是成功失败,而是不同的选项(有选项则为Some()、无选项则为None),所以它的枚举值也很清楚,只有Some()和None两个值。

其次,它们之间可以互相转化。对于错误处理来说,match处理Result或Option时,都要处理错误或None等异常值,就实现了Rust的最重要的错误处理逻辑。

错误处理的控制语法的具体分析和举例

1.Rust的第一类错误处理模式:忽略错误,不处理错误:

(1) unwrap()

在 Rust 中,unwrap() 方法用于从 Result 类型中提取成功时的返回值。如果 Result 类型的值是 Ok(表示成功),则 unwrap() 方法将返回 T;如果 Result 类型的值是 Err(表示失败),则 unwrap() 方法将触发一个 panic,抛出一个 E 类型的错误。如果您在调用 unwrap() 方法时遇到错误,说明您正在处理一个 Err 类型的值,即失败的情况。在这种情况下,您应该使用其他方法来处理错误,而不是直接使用 unwrap() 方法。总之,在处理 Result 类型时,应该始终考虑可能的失败情况,并使用适当的方法来处理错误。直接使用 unwrap() 方法可能会导致程序崩溃(panic),因此应该谨慎使用。只应该在非生产的代码中使用。

(2) .fn()?符号

这个符号在Rust中的术语是“提前返回选项”(early return option),作用等同于unwrap()。只允许用于返回Result或Option类型的函数之后。在rust的早期版本中,有个try!宏具有等效的功能。

代码演示:(演示打开文件,返回Result 类型的值,然后可以被main()中的match方式处理):

use std::fs::File;
use std::io::Error;

fn open_file(file_path: &str) -> Result {
    let mut file = File::open(file_path)?;
    Ok(file)
    
}

这段函数内部使用File::open(file_path)?; 打开指定路径的文件,open()是rust的内部函数,原始定义为:

pub fn open

(path: P) -> io::Result

而演示代码中有意忽略了错误的情况,以?结尾,来调用open()函数:

let mut file = File::open(file_path)?;

2.Rust的第二类错误处理模式:对错误做自定义信息提示:

expect() 可作为代替unwrap()或 ? 相比unwrap(), expect()是一个更好的选择,因为它允许发生错误时打印一个简单的消息并终止运行。

3.Rust的第三类错误处理模式(推荐!):

根据正确和错误情况分开处理,错误还可以进一步分流处理。

(1) match,根据正确和错误情况分开处理。

  • 使用match 分流处理 Result中包含错误的情况处理:
use std::fs::File;
use std::io::Error;

/// 演示打开文件,返回Result 类型的值,然后被main()中的match方式处理
fn open_file(file_path: &str) -> Result {
    let mut file = File::open(file_path)?;
    Ok(file)
}

fn main() {
    let file_path = "file.txt";
    let file = open_file(file_path);
    match file {
        Ok(file) => println!("文件打开成功 {:?}", file),
        Err(error) => println!("文件打开失败 {}", error),
    }
}
  • 使用 match 分流处理 Option包含错误的情况处理:
use std::fs::File;
use std::io::ErrorKind;
use std::io::{Error, Read};

/// 演示文件打开时 如何返回Option 类型值
fn open_file(file_path: &str) -> Option {
    let mut file = File::open(file_path).unwrap();
    Some(file)
}

/// 演示 match 如何处理 Option 类型值,其中有None类型的情况
fn read_file(file: Option) -> Result {
    match file {
        //处理文件的正常情况
        Some(mut file) => {
            let mut buffer = String::new();
            let file_content = file.read_to_string(&mut buffer);
            Ok(buffer)
        }
        //处理文件的异常情况
        None => Err(Error::new(ErrorKind::NotFound, "File not found")),
    }
}

fn main() {
    let file_path = "file.txt";
    let file = open_file(&file_path);
    let strings_in_file = read_file(file);

    /// 在文件 file.txt 不存在的情况下,以下代码会导致软件崩溃。
    println!("{}", strings_in_file.unwrap());

    ///echo "1111">> file.txt
    /// 创建 file.txt文件
    /// 然后重复上面代码
}
  • 使用 map_err() 链式处理

map_err()  将在以后发布的文章中再讲解。本文不做详细介绍。

(2) if let,适合直接在赋值前做错误处理。代码模式为:

let  final_value = if let Some(T) = Rust语句 {
    //语句正确和成功的情况,如获取有效数据,将作为作用域的返回值赋值给final_value 
} else {
    //错误或异常的情况的处理,如赋值为"",同样会作为作用域的返回值赋值给 final_value 
}

以下是一些常见的处理错误的方法:使用 match 表达式:通过使用 match 表达式,您可以根据 Result 类型的值来执行不同的操作。如果是 Ok 类型,可以提取成功的值;如果是 Err 类型,可以处理错误。使用 map_err() 方法:map_err() 方法可以将 Err 类型的值转换为另一种错误类型,并返回一个新的 Result 类型。使用 and_then() 或 or_else() 方法:这些方法可以在成功或失败的情况下执行不同的操作,并返回一个新的 Result 类型。

(3) 使用特定的函数:and_then() 和 or_else()和 ok_or()

这3个函数在Rust中的术语为组合算子,如果你已理解C/C++中的 && 和 ||或 Python中的and以及or语法的意义,那么你大概已经理解了 and_then() 这3个函数的意思。比如 and_then()是当调用者为true或调用者为正常的时候,才会调用and_then(...)函数。那么对于错误处理就非常有用。

下面的代码例子演示了烹饪的逻辑:当有食材的时候,才能按照食谱制作好菜品。隐含的意思就是(错误的情况下),没有食材的情况下,就不用照着食谱做菜了。

#![allow(dead_code)]
#[derive(Debug)] enum Food { CordonBleu, Steak, Sushi }
#[derive(Debug)] enum Day { Monday, Tuesday, Wednesday }
// 我们没有原材料(ingredient)来制作寿司。
fn have_ingredients(food: Food) -> Option {
    match food {
        Food::Sushi => None,
        _           => Some(food),
    }
}
// 我们拥有全部食物的食谱,除了欠缺高超的烹饪手艺。
fn have_recipe(food: Food) -> Option {
    match food {
        Food::CordonBleu => None,
        _                => Some(food),
    }
}
// 做一份好菜,我们需要原材料和食谱这两者。
// 我们可以借助一系列 `match` 来表达相应的逻辑:
// (原文:We can represent the logic with a chain of `match`es:)
fn cookable_v1(food: Food) -> Option {
    match have_ingredients(food) {
        None       => None,
        Some(food) => match have_recipe(food) {
            None       => None,
            Some(food) => Some(food),
        },
    }
}
// 这可以使用 `and_then()` 方便重写出更紧凑的代码:
fn cookable_v2(food: Food) -> Option {
    have_ingredients(food).and_then(have_recipe)
}
fn eat(food: Food, day: Day) {
    match cookable_v2(food) {
        Some(food) => println!("Yay! On {:?} we get to eat {:?}.", day, food),
        None       => println!("Oh no. We don't get to eat on {:?}?", day),
    }
}
fn main() {
    let (cordon_bleu, steak, sushi) = (Food::CordonBleu, Food::Steak, Food::Sushi);
    eat(cordon_bleu, Day::Monday);
    eat(steak, Day::Tuesday);
    eat(sushi, Day::Wednesday);
}

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论