跟着谷歌安卓团队学习Rust-异常错误处理

大家好,我是梦兽编程。欢迎回来与梦兽编程一起刷Rust的系列。

这是由 Google 的 Android开发团队的分享Rust课程。本课程涵盖了 Rust 的方方面面,从基本语法到泛型和错误处理等高级主题。

该课程的最新版本可以在 google.github.io/comprehensi...

如果你喜欢看梦兽编程的版本可以订阅跟着谷歌安卓团队学Rust订阅最新内容,梦兽编程也期待大家关注我的个人网站。

加入梦兽编程微信群,微信搜索【梦兽编程】公众号 回复 111 即可加入交流群与梦兽进行交流。

异常

在日常开发过程,我们会经常遇到运行过程中出现一些奇奇怪怪的错误。有些错误可能会导致程序无法正常执行或者结束。

这种直接让程序崩溃,我们应该尽可能的减少。Rust在运行时发生致命的错误会触发panic来应对。

ini 复制代码
fn main() {
    let v = vec![10, 20, 30];
    println!("v[100]: {}", v[100]);
}

触发panic的情况有三种

  1. 失败的边界检查(如数组越界访问)
  2. 断言失败(例如使用assert!宏)
  3. 使用panic!宏让程序终止。客户端没权限的软件通常的做法

上面的例子就是第一种情况。

我们要如何处理异常呢?

很多时候我们出现异常,是不想程序因为这些错误而直接导致程序无法运行。比如我们想在想打开一个文件,这个文件不存在时会触发panic!,如果你正在开发一款Photoshop的软件,这种迷之操作会直接让用户崩溃。所以我们需要对这些异常做可控的处理。

异常处理的模板,现代语言中golang的异常处理非常简单。

go 复制代码
package main

import (
    "io/ioutil"
    "log"
)

func main() {
    path := "/tmp/dat"  //文件路径
    file, err := readFile(path) 
    if err != nil {
        log.Fatal(err) //错误打印
    }
    println("%s", file) //打印文件内容
}

func readFile(path string) (string, error) {
    dat, err := ioutil.ReadFile(path)  //读取文件内容
    if err != nil {  //判断err是否为nil
        return "", err  //不为nil,返回err结果
    }
    return string(dat), nil  //err=nil,返回读取文件内容
}

在Golang是用if err ≠ nil 这种方式进行处理,Rust提供类似的方式通过match的方式进行异常处理。

「注意:网上很多人会使用unwrap,它主要用于OptionResult的打开其包装的结果。可能因为没有程序检查或校验,潜在的bug可能就出现其中,使得我们程序往往就panic了。这可能使我们最不愿看到的现象。你非常自信的情况下可以unwrap,快速处理简洁的处理是unwrap的特点。」

rust 复制代码
use std::io::Read;
use std::{fs, io};
// 如果你想使用 ? 操作符号简化你的异常处理,需要你在函数执行的地方返回一个Result才能使用
fn read_username(path: &str) -> Result<String, io::Error> {
    let username_file_result = fs::File::open(path);
    let mut username_file = match username_file_result {
        Ok(file) => file,
        Err(err) => return Err(err),
    };

    let mut username = String::new();
    match username_file.read_to_string(&mut username) {
        Ok(_) => Ok(username),
        Err(err) => Err(err),
    }
}

fn main() {
    //fs::write("config.dat", "alice").unwrap();
    let username = read_username("config.dat");
    println!("username or error: {username:?}");
}

如果你想使用?操作符。需要在函数中返回Ruslt才可以使用。我们处理隐藏的经典模板如下:

scss 复制代码
match expression {
    Ok(value) => value,
    Err(err)  => return Err(From::from(err)),
}

这个时候我们就可以使用?操作符来简化我们的操作。

rust 复制代码
use std::error::Error;
use std::fs;
use std::io::Read;

fn read_count(path: &str) -> Result<i32, Box<dyn Error>> {
    let mut count_str = String::new();
    fs::File::open(path)?.read_to_string(&mut count_str)?;
    let count: i32 = count_str.parse()?;
    Ok(count)
}

fn main() {
    fs::write("count.dat", "1i3").unwrap();
    match read_count("count.dat") {
        Ok(count) => println!("Count: {count}"),
        Err(err) => println!("Error: {err}"),
    }
}

std::error::Error 是 Rust提供的一个Trait接口,详细的内容可以查阅doc.rust-lang.org/std/error/t...

rust 复制代码
///自定义类型 Error,实现std::fmt::Debug的trait
#[derive(Debug)]
struct CustomError {
    err: ChildError,
}

///实现Error的trait,因为有子Error:ChildError,需要覆盖source()方法,返回Some(err)
impl std::error::Error for CustomError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.err)
    }
}

使用社区方案处理异常

首先,你需要在Cargo.toml文件中添加thiserror和anyhow作为依赖项:

ini 复制代码
[dependencies]
thiserror = "1.0"
anyhow = "1.0"

然后,你可以使用thiserror来定义一个自定义错误类型:

rust 复制代码
use thiserror::Error;

#[derive(Error, Debug)]
enum CustomError {
    #[error("无法打开文件")]
    FileOpenError(#[from] std::io::Error),
    #[error("无法解析JSON")]
    JsonParseError(#[from] serde_json::Error),
}

我们定义了一个CustomError枚举,它包含了两种可能的错误:文件打开错误和JSON解析错误。使用#[error]属性,我们可以为每个错误变体提供一个描述性消息。#[from]属性则用于从其他错误类型(如std::io::Error和serde_json::Error)自动转换。

接下来,我们可以使用anyhow来处理这些错误:

rust 复制代码
use anyhow::Context;
use std::fs::File;
use std::io::Read;
use serde_json::Value;

fn read_json_file(file_path: &str) -> Result<Value, anyhow::Error> {
    let mut file = File::open(file_path).context("无法打开文件")?;
    let mut content = String::new();
    file.read_to_string(&mut content).context("无法读取文件内容")?;
    let json_value = serde_json::from_str(&content).context("无法解析JSON")?;
    Ok(json_value)
}

fn main() {
    match read_json_file("example.json") {
        Ok(value) => {
            println!("JSON解析成功: {:?}", value);
        }
        Err(e) => {
            eprintln!("发生错误: {}", e);
        }
    }
}

在这个例子中,read_json_file函数尝试打开并读取一个JSON文件。如果任何一步失败,它将使用anyhow::Context来添加上下文信息,并返回一个anyhow::Error。在main函数中,我们调用read_json_file并处理可能发生的错误。 这样,我们就可以使用thiserror来定义自定义错误类型,并使用anyhow来方便地处理和传递这些错误。 希望这篇关于Rust异常错误处理的分享对您有所帮助。Rust以其强大的内存安全特性和高效性能而备受关注,而熟练掌握其异常处理机制对于编写健壮的Rust程序至关重要。如果您希望继续深入学习Rust,或者对编程有任何疑问,欢迎关注我的公众号"梦兽编程",我将持续分享编程知识,并与您一同交流。再次感谢您的阅读,期待与您在"梦兽编程"公众号相见!

本文使用 markdown.com.cn 排版

相关推荐
跟着珅聪学java37 分钟前
spring boot +Elment UI 上传文件教程
java·spring boot·后端·ui·elementui·vue
強云38 分钟前
界面架构- MVP(Qt)
qt·架构
徐小黑ACG2 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
叠叠乐4 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
战族狼魂4 小时前
CSGO 皮肤交易平台后端 (Spring Boot) 代码结构与示例
java·spring boot·后端
niandb5 小时前
The Rust Programming Language 学习 (九)
windows·rust
杉之6 小时前
常见前端GET请求以及对应的Spring后端接收接口写法
java·前端·后端·spring·vue
hycccccch6 小时前
Canal+RabbitMQ实现MySQL数据增量同步
java·数据库·后端·rabbitmq
bobz9657 小时前
k8s 怎么提供虚拟机更好
后端
bobz9657 小时前
nova compute 如何创建 ovs 端口
后端