跟着谷歌安卓团队学习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 排版

相关推荐
好奇的菜鸟5 分钟前
Go语言中的引用类型:指针与传递机制
开发语言·后端·golang
Alive~o.014 分钟前
Go语言进阶&依赖管理
开发语言·后端·golang
许苑向上20 分钟前
Dubbo集成SpringBoot实现远程服务调用
spring boot·后端·dubbo
郑祎亦1 小时前
Spring Boot 项目 myblog 整理
spring boot·后端·java-ee·maven·mybatis
本当迷ya1 小时前
💖2025年不会Stream流被同事排挤了┭┮﹏┭┮(强烈建议实操)
后端·程序员
计算机毕设指导62 小时前
基于 SpringBoot 的作业管理系统【附源码】
java·vue.js·spring boot·后端·mysql·spring·intellij-idea
paopaokaka_luck3 小时前
[371]基于springboot的高校实习管理系统
java·spring boot·后端
捂月4 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
瓜牛_gn4 小时前
依赖注入注解
java·后端·spring