Rust错误处理

这篇具有很好参考价值的文章主要介绍了Rust错误处理。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

返回值和错误处理

panic 深入剖析

主动调用
fn main() {
    panic!("crash and burn");
}
backtrace 栈展开
panic 时的两种终止方式

当出现 panic! 时,程序提供了两种方式来处理终止流程:栈展开和直接终止

何时该使用 panic!

先来一点背景知识,在前面章节我们粗略讲过 Result<T, E> 这个枚举类型,它是用来表示函数的返回结果:

enum Result<T, E> {
    Ok(T),
    Err(E),
}

当没有错误发生时,函数返回一个用 Result 类型包裹的值 Ok(T),当错误时,返回一个 Err(E)。对于 Result 返回我们有很多处理方法,最简单粗暴的就是 unwrap 和 expect,这两个函数非常类似,我们以 unwrap 举例:

use std::net::IpAddr;
let home: IpAddr = "127.0.0.1".parse().unwrap();

返回值和?

对返回的错误进行处理
use std::fs::File;
use std::io::ErrorKind;

fn main() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.txt") {
                Ok(fc) => fc,
                Err(e) => panic!("Problem creating the file: {:?}", e),
            },
            other_error => panic!("Problem opening the file: {:?}", other_error),
        },
    };
}

上面代码在匹配出 error 后,又对 error 进行了详细的匹配解析,最终结果:

  • 如果是文件不存在错误 ErrorKind::NotFound,就创建文件,这里创建文件File::create 也是返回 Result,因此继续用 match 对其结果进行处理:创建成功,将新的文件句柄赋值给 f,如果失败,则 panic
    剩下的错误,一律 panic
  • expect 跟 unwrap 很像,也是遇到错误直接 panic, 但是会带上自定义的错误提示信息,相当于重载了错误打印的函数:
失败就 panic: unwrap 和 expect

在不需要处理错误的场景,例如写原型、示例时,我们不想使用 match 去匹配 Result<T, E> 以获取其中的 T 值,因为 match 的穷尽匹配特性,你总要去处理下 Err 分支。那么有没有办法简化这个过程?有,答案就是 unwrap 和 expect。

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

如果调用这段代码时 hello.txt 文件不存在,那么 unwrap 就将直接 panic:

thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

expect 跟 unwrap 很像,也是遇到错误直接 panic, 但是会带上自定义的错误提示信息,相当于重载了错误打印的函数:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt").expect("Failed to open hello.txt");
}

报错如下:

thread 'main' panicked at 'Failed to open hello.txt: Os { code: 2, kind: NotFound, message: "No such file or directory" }', src/main.rs:4:37
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

传播错误

程序几乎不太可能只有 A->B 形式的函数调用,一个设计良好的程序,一个功能涉及十几层的函数调用都有可能。而错误处理也往往不是哪里调用出错,就在哪里处理,实际应用中,大概率会把错误层层上传然后交给调用链的上游函数进行处理,错误传播将极为常见。

例如以下函数从文件中读取用户名,然后将结果进行返回:

use std::fs::File;
use std::io::{self, Read};

fn read_username_from_file() -> Result<String, io::Error> {
    // 打开文件,f是`Result<文件句柄,io::Error>`
    let f = File::open("hello.txt");

    let mut f = match f {
        // 打开文件成功,将file句柄赋值给f
        Ok(file) => file,
        // 打开文件失败,将错误返回(向上传播)
        Err(e) => return Err(e),
    };
    // 创建动态字符串s
    let mut s = String::new();
    // 从f文件句柄读取数据并写入s中
    match f.read_to_string(&mut s) {
        // 读取成功,返回Ok封装的字符串
        Ok(_) => Ok(s),
        // 将错误向上传播
        Err(e) => Err(e),
    }
}

有几点值得注意:

  • 该函数返回一个 Result<String, io::Error> 类型,当读取用户名成功时,返回 Ok(String),失败时,返回 Err(io:Error)
  • File::open 和 f.read_to_string 返回的 Result<T, E> 中的 E 就是 io::Error
    由此可见,该函数将 io::Error 的错误往上进行传播,该函数的调用者最终会对 Result<String,io::Error> 进行再处理,至于怎么处理就是调用者的事,如果是错误,它可以选择继续向上传播错误,也可以直接 panic,亦或将具体的错误原因包装后写入 socket 中呈现给终端用户。

传播界的大明星: ?

use std::fs::File;
use std::io;
use std::io::Read;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

看到没,这就是排面,相比前面的 match 处理错误的函数,代码直接减少了一半不止.

其实 ? 就是一个宏,它的作用跟上面的 match 几乎一模一样:

let mut f = match f {
    // 打开文件成功,将file句柄赋值给f
    Ok(file) => file,
    // 打开文件失败,将错误返回(向上传播)
    Err(e) => return Err(e),
};

如果结果是 Ok(T),则把 T 赋值给 f,如果结果是 Err(E),则返回该错误,所以 ? 特别适合用来传播错误。

虽然 ? 和 match 功能一致,但是事实上 ? 会更胜一筹。

想象一下,一个设计良好的系统中,肯定有自定义的错误特征,错误之间很可能会存在上下级关系,例如标准库中的 std::io::Error 和 std::error::Error,前者是 IO 相关的错误结构体,后者是一个最最通用的标准错误特征,同时前者实现了后者,因此 std::io::Error 可以转换为 std:error::Error。

明白了以上的错误转换,? 的更胜一筹就很好理解了,它可以自动进行类型提升(转换):

fn open_file() -> Result<File, Box<dyn std::error::Error>> {
    let mut f = File::open("hello.txt")?;
    Ok(f)
}

上面代码中 File::open 报错时返回的错误是 std::io::Error 类型,但是 open_file 函数返回的错误类型是 std::error::Error 的特征对象,可以看到一个错误类型通过 ? 返回后,变成了另一个错误类型,这就是 ? 的神奇之处。

根本原因是在于标准库中定义的 From 特征,该特征有一个方法 from,用于把一个类型转成另外一个类型,? 可以自动调用该方法,然后进行隐式类型转换。因此只要函数返回的错误 ReturnError 实现了 From 特征,那么 ? 就会自动把 OtherError 转换为 ReturnError。

这种转换非常好用,意味着你可以用一个大而全的 ReturnError 来覆盖所有错误类型,只需要为各种子错误类型实现这种转换即可。

use std::fs::File;
use std::io;
use std::io::Read;

fn read_username_from_file() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}

? ? 还能实现链式调用,File::open 遇到错误就返回,没有错误就将 Ok 中的值取出来用于下一个方法调用,简直太精妙了.

use std::fs;
use std::io;

fn read_username_from_file() -> Result<String, io::Error> {
    // read_to_string是定义在std::io中的方法,因此需要在上面进行引用
    fs::read_to_string("hello.txt")
}

从文件读取数据到字符串中,是比较常见的操作,因此 Rust 标准库为我们提供了 fs::read_to_string 函数,该函数内部会打开一个文件、创建 String、读取文件内容最后写入字符串并返回,因为该函数其实与本章讲的内容关系不大,因此放在最后来讲,其实只是我想震你们一下 😃

? 用于 Option 的返回

? 不仅仅可以用于 Result 的传播,还能用于 Option 的传播,再来回忆下 Option 的定义:

pub enum Option<T> {
    Some(T),
    None
}

Result 通过 ? 返回错误,那么 Option 就通过 ? 返回 None:

fn first(arr: &[i32]) -> Option<&i32> {
   let v = arr.get(0)?;
   Some(v)
}
新手用 ? 常会犯的错误

初学者在用 ? 时,老是会犯错,例如写出这样的代码:

fn first(arr: &[i32]) -> Option<&i32> {
   arr.get(0)?
}

这段代码无法通过编译,切记:? 操作符需要一个变量来承载正确的值,这个函数只会返回 Some(&i32) 或者 None,只有错误值能直接返回,正确的值不行,所以如果数组中存在 0 号元素,那么函数第二行使用 ? 后的返回类型为 &i32 而不是 Some(&i32)。因此 ? 只能用于以下形式:

let v = xxx()?;
xxx()?.yyy()?;
带返回值的 main 函数

在了解了 ? 的使用限制后,这段代码你很容易看出它无法编译:

use std::fs::File;

fn main() {
    let f = File::open("hello.txt")?;
}

运行后会报错:

$ cargo run
   ...
   the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`)
 --> src/main.rs:4:48
  |
3 | fn main() {
  | --------- this function should return `Result` or `Option` to accept `?`
4 |     let greeting_file = File::open("hello.txt")?;
  |                                                ^ cannot use the `?` operator in a function that returns `()`
  |
  = help: the trait `FromResidual<Result<Infallible, std::io::Error>>` is not implemented for `()`

因为 ? 要求 Result<T, E> 形式的返回值,而 main 函数的返回是 (),因此无法满足,那是不是就无解了呢?

实际上 Rust 还支持另外一种形式的 main 函数:

use std::error::Error;
use std::fs::File;

fn main() -> Result<(), Box<dyn Error>> {
    let f = File::open("hello.txt")?;

    Ok(())
}

这样就能使用 ? 提前返回了,同时我们又一次看到了Box 特征对象,因为 std::error:Error 是 Rust 中抽象层次最高的错误,其它标准库中的错误都实现了该特征,因此我们可以用该特征对象代表一切错误,就算 main 函数中调用任何标准库函数发生错误,都可以通过 Box<dyn Error> 这个特征对象进行返回。文章来源地址https://www.toymoban.com/news/detail-741301.html

到了这里,关于Rust错误处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 30天拿下Rust之错误处理

    概述         在软件开发领域,对错误的妥善处理是保证程序稳定性和健壮性的重要环节。Rust作为一种系统级编程语言,以其对内存安全和所有权的独特设计而著称,其错误处理机制同样体现了Rust的严谨与实用。在Rust中,错误处理通常分为两大类:不可恢复的错误和可

    2024年03月21日
    浏览(66)
  • Rust Web 全栈开发之 Web Service 中的错误处理

    数据库 数据库错误 串行化 serde 错误 I/O 操作 I/O 错误 Actix-Web 库 Actix 错误 用户非法输入 用户非法输入错误 编程语言常用的两种错误处理方式: 异常 返回值( Rust 使用这种) Rust 希望开发者显式的处理错误,因此,可能出错的函数返回Result 枚举类型,其定义如下: 例子 在

    2024年02月07日
    浏览(40)
  • Rust之构建命令行程序(三):重构改进模块化和错误处理

    Windows 10 Rust 1.74.1   VS Code 1.85.1 这次创建了新的工程minigrep. 为了改进我们的程序,我们将修复与程序结构及其处理潜在错误的方式有关的四个问题。首先,我们的 main 函数现在执行两项任务:解析参数和读取文件。随着我们程序的增长, main 处理的独立任务的数量也会增加。随

    2024年01月18日
    浏览(48)
  • Rust腐蚀申述教程Rust被办申述处理

    玩Rust很难避免被办,但有些被办是理所当。例如:开G、开宏或者其他作弊行为影响了游戏平衡,本指南主要针对绿色玩家被误封的情况。 一、不同类型的封禁 首先要了解不同的封禁类型,并不是所有的封禁都是公平公正的,特别是国人在外服或者社区服游戏时经常被办出服

    2024年02月06日
    浏览(35)
  • Rust处理JSON

    基本操作 Cargo.toml: main.rs: 输出为: 嵌套结构体 warp [1] 返回不同的结构(一般用枚举来解决) 参考资料 [1] warp: https://github.com/seanmonstar/warp 本文由 mdnice 多平台发布

    2024年02月11日
    浏览(56)
  • rust版本更新错误记录:Os { code: 5, kind: PermissionDenied }

    使用 rustup update 更新 rust 版本时遇到错误: info: cleaning up downloads tmp directories thread ‘main’ panicked at ‘Unable to clean up C:UsersGrapeX.rustuptmp: Os { code: 5, kind: PermissionDenied, message: “拒绝访问。” }’, srcutilsutils.rs:650:13 stack backtrace: note: Some details are omitted, run with RUST_BACKTRACE=full

    2024年02月16日
    浏览(53)
  • RUST Rover 条件编译 异常处理

    会报异常 error: failed to parse manifest at C:UserstopmaRustroverProjectsuntitled2Cargo.toml 网上说明 这样处理 https://course.rs/cargo/reference/features/intro.html RUST 圣经里描述

    2024年04月09日
    浏览(35)
  • Rust中的高吞吐量流处理

    本篇文章主要介绍了Rust中流处理的概念、方法和优化。作者不仅介绍了流处理的基本概念以及Rust中常用的流处理库,还使用这些库实现了一个流处理程序。 最后,作者介绍了如何通过测量空闲和阻塞时间来优化流处理程序的性能,并将这些内容同步至Twitter和blog。 此外,作

    2024年02月14日
    浏览(48)
  • Rust中的字符串处理及相关方法详解

    在Rust中,字符串是一种非常重要的数据类型,而 String 类型则提供了对动态可变字符串的支持。本文将介绍一些常见的字符串处理方法以及相关示例代码。 在Rust中,有多种方式创建字符串,以下是一些常见的例子: push_str()方法 push_str() 方法用于将一个字符串切片附加到 S

    2024年02月19日
    浏览(36)
  • 【Rust】——通过Deref trait将智能指针当作常规引用处理

    💻博主现有专栏:                 C51单片机(STC89C516),c语言,c++,离散数学,算法设计与分析,数据结构,Python,Java基础,MySQL,linux,基于HTML5的网页设计及应用,Rust(官方文档重点总结),jQuery,前端vue.js,Javaweb开发,Python机器学习等 🥏主页链接:     

    2024年04月26日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包