0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

rust语言基础学习: rust中的错误处理

冬至子 来源:山川与湖水 作者:山川与湖水 2023-05-22 16:28 次阅读

错误是软件中不可避免的,所以 Rust 有一些处理出错情况的特性。在许多情况下,Rust 要求你承认错误的可能性,并在你的代码编译前采取一些行动。这一要求使你的程序更加健壮,因为它可以确保你在将代码部署到生产环境之前就能发现错误并进行适当的处理。

Rust中的错误可分为可 恢复错误 (recoverable)和 不可恢复错误 (unrecoverable)

可恢复错误代表一种可以恢复的情况下失败,可以向用户报告问题并重试操作,例如未找到文件;

不可恢复错误代表一种不可处理的状态,会导致程序 崩2 ,例如尝试访问超过数组结尾的位置;

对比其他编程语言的错误处理:
1. Java语言采用了异常机制的方式来处理,并没有明确区分可恢复错误和不可恢复错误。
(ps: 虽然Java的异常给出了Throwable, Error, Exception, RuntimeException的
继承关系体系,异常分为checked exception和uncheced exceppion,但在异常传播上
采用的是通过栈回溯的方式一层层传递,直到出现捕获异常的地方。) Java语言这种异常
处理方式的优点是简化了错误处理流程,但在运行时开销比使用返回值返回错误信息的方式要大很多。
2. Go语言是明确区分可恢复错误(error)和不可恢复错误(panic)的。Go对可恢复错误
采用了以函数返回错误值的形式,在函数返回时额外返回一个错误对象(error),这种方式的优点
是错误处理的运行时开销小,缺点是返回的错误必须处理或者显式传播返回给上级调用,
因此一个Go程序代码中会有大量的if err!= nil {return err;}。

Rust中没有异常,对于可恢复错误使用了类型Result,即函数返回的错误信息通过类型系统描述。对于在程序遇到不可恢复的错误时panic!时停止执行

1. Result和可恢复错误

Result是一个枚举类型,其定义如下:

#[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_type"]
#[stable(feature = "rust1", since = "1.0.0")]
pub enum Result

Result枚举有两个成员,OkErrTE是泛型参数T代表成功返回的Ok成员中的数据类型。E代表失败返回的Err成员中的错误的类型。有了这两个泛型参数,可以将Result枚举作为函数的返回值,用于各种场景下的可恢复错误的处理,当函数成功时返回Ok(T),失败时返回Err(E)

Result的定义上面有一个must_use的标注,rust的编译器会对must_use标注的类型做特殊处理,如果该类型对应的值没有被显式使用,则就会有一个警告。例如下面的代码。

fn foo() -> Result<(), String> {
    Ok(())
}


fn main() {
    foo(); // unused `std::result::Result` that must be used
}

代码在调用foo函数时,忽略了返回值Result,因为Result上有must_use标注,所以Rust的编译器在编译时会报一个警告:

warning: unused `Result` that must be used
 --> src/main.rs:7:5
  |
7 |     foo(); // unused `std::result::Result` that must be used
  |     ^^^^^^
  |
  = note: `#[warn(unused_must_use)]` on by default
  = note: this `Result` may be an `Err` variant, which should be handled

1.1 匹配不同的错误原因

在处理错误时,很多时候需要针对不同的错误原因进行不同的处理。下面来学习一下rust标准库中的std::io module中是如何设计错误处理的。

std::io中定义了一个std::io::Result:

#[stable(feature = "rust1", since = "1.0.0")]
pub type Result

io::Result的定义可以看出,io::Result实际上是result::Result的别名。 io::Result中的Err成员类型是io::Error

io::Error是一个结构体,它由一个kind()方法签名是pub fn kind(&self) -> ErrorKind,返回描述错误原因枚举ErrorKind

ErrorKind的成员是各种io错误原因,比如NotFound, PermissionDenied

因此如果函数返回io::Result,失败时返回的是io::Error时,就可以调用kind方法,进一步匹配不同的错误原因进行不同处理。

use std::fs::File;
use std::io::ErrorKind;


fn main() {
    let f = File::open("hello.txt").unwrap_or_else(|err| {
        match err.kind() {
            ErrorKind::NotFound => File::create("hello.tx").unwrap_or_else(|error| {
                panic!("Problem creating the file: {:?}", error);
            }), // 匹配错误原因, 对于文件不存在的错误处理为创建文件
            other_error_kind => panic!("Problem opening the file: {:?}", other_error_kind)
        }
    });
    println!("{:?}", f);
}

例2中还用到了Result的unwrap_or_else方法,Result类型定义了很多辅助方法来处理各种情况。除了unwrap_or_else外,还有:

  • unwrap方法: 如果Result的值是成员Okunwrap就返回Ok的值;如果Result的值是成员Errunwrap就会调用panic!
  • expect方法: 与unwrap的使用方式一样,允许我们传参指定panic!的信息

1.2 使用?操作符传播错误

经常在编写一个函数实现时会调用另一个返回Result的函数,除了在这个函数中处理错误之外,还可以选择将错误传播到上游调用者,这就是传播错误。

rust还提供了强大的?操作符,如果我们只想要传播错误,而不想直接处理,可以使用?操作符。

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


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)
}

代码中第6行的?操作符会被展开成类似下面的代码:

match result {
    Ok(v) => v,
    Err(e) => Err(e.into())
}
```Result 值之后的 ? 作用为与 match 表达式有着完全相同的工作方式。如果 Result 的值是 Ok,这个表达式将会返回 Ok 中的值而程序将继续执行。如果值是 ErrErr 中的值将作为整个函数的返回值,就好像使用了 return 关键字一样,这样错误值就被传播给了调用者。

match 表达式与 ? 运算符所做的有一点不同:? 运算符所使用的错误值被传递给了 from 函数,它定义于标准库的 From trait 中,其用来将错误从一种类型转换为另一种类型。当 ? 运算符调用 from 函数时,收到的错误类型被转换为由当前函数返回类型所指定的错误类型。

## 2. panic! 和不可恢复错误

突然有一天,代码出问题了,程序崩溃,对于这种情况,Rust提供了一个panic!宏,当执行这个宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug,Rust程序员可以让 Rust 在 panic 发生时打印调用堆栈(call stack)以便于定位 panic 的原因。

在实践中有两种方法造成 panic:

* 执行会造成代码 panic 的操作,比如访问超过数组结尾的内容
* 显式调用 panic! 宏,比如`panic!("crash and burn");`。

panic!表示不可恢复的错误,希望程序马上终止运行并得到崩溃信息。

rust标准库还提供了`catch_unwind()`,可以把panic的调用栈回溯到catch_unwind的时候。

use std::panic;

fn main() {

let result = panic::catch_unwind(|| {

panic!("crash");

});

if result.is_err() {

println!("panic reover: {:#?}", result);

}

println!("exit ok!");

}

代码运行结果如下:

thread 'main' panicked at 'crash', src/main.rs:4:9

note: run with RUST_BACKTRACE=1 environment variable to display

a backtracepanic reover: Err(

Any { .. },

)

exit ok!

### 2.1 使用 `panic!` 的 backtrace

可以使用 `RUST_BACKTRACE=1 cargo run` 来得到一个 backtrace,backtrace 是一个执行到目前位置所有被调用的函数的列表。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件。这就是问题的发源地。这一行往上是你的代码所调用的代码;往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。

// src/main.rs

fn main() {

let v = vec![1, 2, 3];

v[99];

}

$ RUST_BACKTRACE=1 cargo run

thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 99', src/main.rs:4:5

stack backtrace:

0: rust_begin_unwind at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/std/src/panicking.rs:584:5

1: core::panicking::panic_fmt at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:142:14

2: core::panicking::panic_bounds_check at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/panicking.rs:84:5

3: >::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:242:10

4: core::slice::index:: for [T]>::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/slice/index.rs:18:9

5: [alloc::vec::Vec as core::ops::index::Index*>::index at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/alloc/src/vec/mod.rs:2591:9

6: panic::main at ./src/main.rs:4:5

7: core::ops::function::FnOnce::call_once at /rustc/e092d0b6b43f2de967af0887873151bb1c0b18d3/library/core/src/ops/function.rs:248:5

note: Some details are omitted, run with RUST_BACKTRACE=full for a verbose backtrace.*](alloc::vec::Vec%3CT,A)

[**

## 总结

Rust 的错误处理功能被设计为帮助你编写更加健壮的代码。`panic!` 宏代表一个程序**无法处理**的状态,并停止执行而不是使用无效或不正确的值继续处理。Rust 类型系统的 `Result` 枚举代表操作可能会在一种**可以恢复**的情况下失败,可以使用 `Result` 来告诉代码调用者他需要处理潜在的成功或失败。在适当的场景使用 `panic!``Result` 将会使你的代码在面对不可避免的错误时显得更加可靠。

* 可恢复错误:想向用户报告问题并**重试**操作,Result处理;
* 不可恢复错误:导致程序**崩溃的**panic,catch_unwind() 栈回溯;
* match匹配: 处理返回值`Result`,**匹配**出不同错误的原因;
* ?运算符: **传播错误** ,将错误从一种类型转换为另一种类型,返回给调用者;

**](alloc::vec::Vec%3CT,A)

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • JAVA
    +关注

    关注

    19

    文章

    2967

    浏览量

    104742
  • 编译器
    +关注

    关注

    1

    文章

    1634

    浏览量

    49128
  • rust语言
    +关注

    关注

    0

    文章

    57

    浏览量

    3009
收藏 人收藏

    评论

    相关推荐

    聊聊Rust与C语言交互的具体步骤

    rust FFI 是rust与其他语言互调的桥梁,通过FFI rust 可以有效继承 C 语言的历史资产。本期通过几个例子来聊聊
    发表于 07-06 11:15 1709次阅读

    Rust语言错误处理的机制

    可能的错误,实际运行仍然可能出现各种各样的错误,比如文件不存在、网络连接失败等等。对于这些不可预测的错误,我们必须使用错误处理机制来进行
    的头像 发表于 09-19 14:54 1413次阅读

    基于Rust语言Hash特征的基础用法和进阶用法

    Rust语言是一种系统级编程语言,具有高性能、安全、并发等特点,是近年来备受关注的新兴编程语言。在Rust
    的头像 发表于 09-19 16:02 1461次阅读

    Rust语言如何与 InfluxDB 集成

    的数据处理和存储能力。 本教程将介绍 Rust 语言如何与 InfluxDB 集成,包括基础用法和进阶用法和完整的示例代码。 基础用法 安装 InfluxDB Rust 客户端 首先,
    的头像 发表于 09-30 16:45 1165次阅读

    基于Rust语言中的生命周期

    Rust是一门系统级编程语言具备高效、安和并发等特,而生命周期是这门语言中比较重要的概念之一。在这篇教程,我们会了解什么是命周期、为什么需要生命周期、如何使用生命周期,同时我们依然会
    的头像 发表于 09-19 17:03 903次阅读

    Rust 语言中的 RwLock内部实现原理

    Rust是一种系统级编程语言,它带有严格的内存管理、并发和安全性规则,因此很受广大程序员的青睐。RwLock(读写锁)是 Rust 中常用的线程同步机制之一,本文将详细介绍 Rust
    的头像 发表于 09-20 11:23 876次阅读

    只会用Python?教你在树莓派上开始使用Rust

    如果您对编程感兴趣,那么您可能听说过Rust。该语言由Mozilla设计,受到开发人员的广泛喜爱,并继续在奉献者成长。Raspberry Pi是小型计算机的瑞士军刀,非常适合学习代码
    发表于 05-20 08:00

    如何用 rust 语言开发 stm32

    本文介绍如何用 rust 语言开发 stm32。开发平台为 linux(gentoo)。硬件准备本文使用的芯片为 STM32F103C8T6。该芯片性价比较高,价格低廉,适合入门学习。需要
    发表于 11-26 06:20

    RUST在嵌入式开发的应用是什么

    Rust是一种编程语言,它使用户能够构建可靠、高效的软件,尤其是用于嵌入式开发的软件。它的特点是:高性能:Rust具有惊人的速度和高内存利用率。可靠性:在编译过程可以消除内存
    发表于 12-24 08:34

    如何利用C语言去调用rust静态库呢

    语言的感觉,要做不少的对接工作。也用过Lua,感觉也差不多。评估学习评估Rust语言时,感觉性能和体积应该都不会有太大的问题。加上语言本身
    发表于 06-21 10:27

    Rust代码中加载静态库时,出现错误 ` rust-lld: error: undefined symbol: malloc `怎么解决?

    时,出现错误 ` [i]rust-lld: error: undefined symbol: malloc `。如何将这些定义包含在我的静态库
    发表于 06-09 08:44

    微软开发基于Rust的新编程语言,将很快开源

    使用Rust重写各种产品,因为在过去的十年里,微软70%以上的安全补丁都提供了与内存相关的错误,而Rust正是解决这个问题的良药。 而根据ZDNet的报导,近日在一次演讲,谈到微软为
    的头像 发表于 12-03 10:36 3920次阅读

    以调试Rust的方式来学习Rust

    在我上一篇 关于 Rustup 的文章 ,我向你们展示了如何安装 Rust 工具链。但是,如果不能上手操作一下 Rust 的话下载工具链又有什么用?学习任何
    的头像 发表于 01-03 14:56 909次阅读

    Rust错误处理方法

    Rust 没有提供类似于 Java、C++ 的 Exception 机制,而是使用 Result 枚举的方式来实现。
    的头像 发表于 02-20 09:37 949次阅读

    rust语言基础学习: 智能指针之Cow

    Rust与借用数据相关的三个trait: Borrow, BorrowMut和ToOwned。理解了这三个trait之后,再学习Rust
    的头像 发表于 05-22 16:13 2917次阅读