Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Rust错误处理

Rust错误处理

作者头像
hotarugali
发布于 2022-03-18 08:48:37
发布于 2022-03-18 08:48:37
80700
代码可运行
举报
运行总次数:0
代码可运行

1. 简介

在很多情况下,Rust 要求你承认出错的可能性,并在编译代码之前就采取行动。这些要求使得程序更为健壮,它们确保了你会在将代码部署到生产环境之前就发现错误并正确地处理它们!Rust 将错误组合成两个主要类别:「可恢复错误」(recoverable)和「不可恢复错误」(unrecoverable)。

  • 可恢复错误通常代表向用户报告错误和重试操作是合理的情况,比如未找到文件。
  • 不可恢复错误通常是 bug 的同义词,比如尝试访问超过数组结尾的位置。

大部分语言并不区分这两类错误,并采用类似异常这样方式统一处理他们。Rust 并没有异常,但是有可恢复错误 Result<T, E> 和不可恢复(遇到错误时停止程序执行)错误 panic!

  • panic! 宏代表一个程序无法处理的状态,并停止执行而不是使用无效或不正确的值继续处理。
  • Result 枚举代表操作可能会在一种可以恢复的情况下失败。可以使用 Result 来告诉代码调用者他需要处理潜在的成功或失败。在

适当的场景使用 panic!Result 将会使代码在面对不可避免的错误时显得更加可靠。

2. panic! 与不可恢复错误

  • 当执行 panic! 宏时,程序会打印出一个错误信息,展开并清理栈数据,然后接着退出。出现这种情况的场景通常是检测到一些类型的 bug,而且程序员并不清楚该如何处理它。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
fn main() {
    panic!("crash and burn");
}

2.1 栈展开或终止

  • 当出现 panic 时,程序默认会开始「展开」(unwinding),这意味着 Rust 会回溯栈并清理它遇到的每一个函数的数据,不过这个回溯并清理的过程有很多工作。
  • 另一种选择是直接「终止」(abort),这会不清理数据就退出程序。那么程序所使用的内存需要由操作系统来清理。如果你需要项目的最终二进制文件越小越好,panic 时通过在 Cargo.toml[profile] 部分增加 panic = 'abort',可以由展开切换为终止。例如,如果你想要在 release 模式中 panic 时直接终止:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
[profile.release]
panic = 'abort'

2.2 panic! 回溯

cargo run 时,我们可以设置 RUST_BACKTRACE=1 环境变量来回溯 panic! 清理过程 backtrace。Rust 的 backtrace 跟其他语言中的一样:阅读 backtrace 的关键是从头开始读直到发现你编写的文件,这就是问题的发源地。这一行往上是你的代码所调用的代码,往下则是调用你的代码的代码。这些行可能包含核心 Rust 代码,标准库代码或用到的 crate 代码。panic! 回溯输出可能因不同的操作系统和 Rust 版本而有所不同。

【注】为了获取带有这些信息的 backtrace,必须启用 debug 标识。当不使用 --release 参数运行 cargo buildcargo run 时 debug 标识会默认启用。

3. Result 与可恢复错误

大部分错误并没有严重到需要程序完全停止执行。有时,一个函数会因为一个容易理解并做出反应的原因失败。

3.1 Result 类型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
enum Result<T, E> {
    Ok(T),
    Err(E),
}

其中,TE 是泛类型参数,T 代表成功时返回的 Ok 成员中的数据类型,而 E 代表失败时返回的 Err 成员中的错误类型。

3.2 匹配不同错误

以打开文件为例,有多种原因导致 File:open 失败。我们真正希望的是对不同的错误原因采取不同的行为:如果 File::open 因为文件不存在而失败,我们希望创建这个文件并返回新文件的句柄。如果 File::open 因为任何其他原因失败,例如没有打开文件的权限,则希望 panic!

File::open 返回的 Err 成员中的值类型 io::Error,它是一个标准库中提供的结构体。这个结构体有一个返回 io::ErrorKind 值的 kind 方法可供调用。io::ErrorKind 是一个标准库提供的枚举,它的成员对应 io 操作可能导致的不同错误类型。其中 ErrorKind::NotFound 代表尝试打开的文件并不存在。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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),
        },
    };
}

3.3 panic! 简写

match 能够胜任它的工作,不过它可能有点冗长并且不总是能很好的表明其意图。Result<T, E> 类型定义了很多辅助方法来处理各种情况。

  • 其中一个方法为 unwrap,一下两种打开文件的处理方式等价:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
use std::fs::File;

fn main() {
    // 方式一
    let f = File::open("hello.txt").unwrap();

    // 方式二
    let f = File::open("hello.txt");
    let f = match f {
        Ok(file) => file,
        Err(error) => {
            panic!("Problem opening the file: {:?}", error)
        },
    };
}
  • 另一个方法 expect 允许我们定义 panic! 的错误信息,使用 expect 而不是 unwrap 并提供一个好的错误信息可以表明你的意图并更易于追踪 panic 的根源。expect 的语法格式举例如下:
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
use std::fs::File;

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

【注】expectunwrap 的使用方式一样:返回函数成功调用的返回值或调用 panic! 宏。expect 用来调用 panic! 的错误信息将会作为参数传递给 expect,而不像 unwrap 那样使用默认的 panic! 信息。

3.4 传播错误

当编写一个其实现会调用一些可能会失败的操作的函数时,除了在这个函数中处理错误外,还可以选择让调用者知道这个错误并决定该如何处理。这被称为「传播」(propagating)错误,这样能更好的控制代码调用,因为比起你代码所拥有的上下文,调用者可能拥有更多信息或逻辑来决定应该如何处理错误。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
use std::io;
use std::io::Read;
use std::fs::File;

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

除了可以使用上述例子的繁琐 match 分支来实现传播错误外,Rust 还提供了 ? 运算符用于简写传播错误。Result 值之后的 ? 的作用是:

  • 如果 Result 的值是 Ok,这个表达式将会返回 Ok 中的值而程序将继续执行。
  • 如果 Result 的值是 ErrErr 中的值将作为整个函数的返回值,就好像使用了 return 关键字一样,这样错误值就被传播给了调用者。
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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)
}

甚至可以在 ? 之后直接使用链式方法调用来进一步缩短代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
use std::io;
use std::io::Read;
use std::fs::File;

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

【注】? 运算符所使用的错误值被传递给了 from 函数,它定义于标准库的 From trait 中,其用来将错误从一种类型转换为另一种类型。当 ? 运算符调用 from 函数时,收到的错误类型将被转换为由当前函数返回类型所指定的错误类型。

5. 错误处理指导原则

5.1 使用 panic!

在当有可能会导致有害状态的情况下建议使用 panic! —— 在这里,有害状态是指当一些假设、保证、协议或不可变性被打破的状态,例如无效的值、自相矛盾的值或者被传递了不存在的值等,外加如下几种情况:

  • 有害状态并不包含预期会偶尔发生的错误。
  • 在此之后代码的运行依赖于不处于这种有害状态。
  • 当没有可行的手段来将有害状态信息编码进所使用的类型中的情况。

5.2 使用 Result

当错误预期会出现时,返回 Result 要比调用 panic! 更为合适。这样的例子包括解析器接收到格式错误的数据,或者 HTTP 请求返回了一个表明触发了限流的状态。在这些例子中,应该通过返回 Result 来表明失败预期是可能的,这样将有害状态向上传播,调用者就可以决定该如何处理这个问题。使用 panic! 来处理这些情况就不是最好的选择。

4. 具体错误

4.1 mismatched types

该错误信息表示代码中出现了「类型不匹配」。除了 Rust 中已定义的数据类型外,错误信息中还会使用空元组 () 来表示空类型。即使用空元组 () 表示没有返回值。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
Rust学习笔记之错误处理
今天,我们继续「Rust学习笔记」的探索。我们来谈谈关于「错误处理」的相关知识点。
前端柒八九
2023/03/23
5640
Rust学习笔记之错误处理
Rust错误处理
不可恢复错误通常是非常严重的,例如:程序一开始读取配置文件失败或者连接数据库失败,诸如此类导致程序运行发生致命错误的,可以使用不可恢复错误。在rust中,触发不可恢复错误使用panic即可。 触发panic可以分为被动触发和主动调用两种方式。
zy010101
2023/03/27
7890
【Rust学习】21_错误处理_Result
大多数错误没有严重到需要程序完全停止的程度。有时,当函数失败时,这是由于您可以轻松解释和响应的原因。例如,如果您尝试打开一个文件,但该操作失败,因为该文件不存在,您可能希望创建该文件,而不是终止进程。
思索
2025/01/06
1410
【Rust学习】21_错误处理_Result
周末学了点 Rust简介工具链宏(macros)返回值和错误处理Ownership 和生命周期闭包小结参考文档
Rust 是最近几年开始兴起的编程语言,虽然目前还没看到要像 Go 一样”大火“的趋势。但是,官网的一些 featuring 看着就很让人心动(虽然还不知道现实如何~)。
linjinhe
2018/08/10
9990
Rust入坑指南:亡羊补牢
对于类型系统,熟悉Java的同学应该比较清楚。例如我们给一个接收参数为int的函数传入了字符串类型的变量。这是由编译器帮我们处理的。
Jackeyzhe
2020/03/10
9000
【Rust 基础篇】Rust 错误处理详解
在软件开发中,错误处理是一项重要的任务。Rust 提供了一套强大的错误处理机制,使开发者能够有效地处理和管理错误。本篇博客将详细解析 Rust 中的错误处理机制,包括错误类型、错误传播、Result 类型以及错误处理的最佳实践。
繁依Fanyi
2023/10/12
5160
Rust学习笔记Day21 为什么Rust的错误处理与众不同?
在 C 语言中,如果 fopen(filename) 无法打开文件,会返回 NULL,调用者通过判断返回值是否为 NULL,来进行相应的错误处理。
用户1072003
2023/02/23
7030
Rust学习笔记Day21 为什么Rust的错误处理与众不同?
30.Rust-错误处理
Rust 语言也有错误这个概念,而且把错误分为两大类:可恢复 和 不可恢复,相当于其它语言的 异常 和 错误。
面向加薪学习
2022/06/30
3880
【Rust blog】细说Rust错误处理
这篇文章写得比较长,全文读完大约需要15-20min,如果对Rust的错误处理不清楚或还有些许模糊的同学,请静下心来细细阅读。当读完该篇文章后,可以说对Rust的错误处理可以做到掌握自如。
MikeLoveRust
2020/03/03
3.6K0
Rust竟然没有异常处理?
学习Rust最好的方法,是和其他主流语言,比如Java、Python进行对比学习。不然怎么能get到它的特别呢?
袁承兴
2020/08/17
1.8K0
Rust语法入门
Rust 是一种系统级编程语言,它的设计目标是提供高性能、安全性和并发性。Rust 的主要优势包括:
码客说
2023/04/17
1.4K0
【Rust每周一库】failure - 错误处理库
错误处理在生产级别的代码中一直都是一个重点。在原型阶段,愉快地使用unwrap可以确保思路和精力被集中用在业务逻辑开发上。不过对于最终要上线的代码,优雅的处理错误却是至关重要的。原生Rust错误处理的工具有std::error::Error(一般我们会看到Box<dyn Error>的形式),?操作符以及enum供我们自定义错误类型。这本身就可以作为一个专题来讨论。而今天我们就来简单介绍一下failure库以及其背后的错误处理哲学。
MikeLoveRust
2020/03/31
1.4K0
从C++转向Rust:两大主题值得关注!
导语 | 云加社区祝大家新年快乐!新春假期结束的第一篇干货,为大家带来的是从C++转向Rust主题的内容。在日常的开发过程中,长期使用C++,在使用Rust的过程中可能会碰到一些问题。本文是From C++ To Rust的第二篇,在这一篇里,主要介绍错误处理和生命周期两个主题。 此前,我介绍了其中思维方式的转变(mind shift):《详细解答!从C++转向Rust需要注意哪些问题?》 一、错误处理 (一)C++ 任何生产级别的软件开发中,错误处理都需要被妥善考虑。C++通常会有两种错误处理的风格:
腾讯云开发者
2022/02/10
8480
Rust中的错误处理机制
在大多数现代语言中,都拥有一套完善的错误处理机制(error handing)。在一些典型的面向对象语言,例如 Java 和 Python 中,错误使用 try…catch 语法进行处理,但这种机制却存在显著的问题。
端碗吹水
2022/06/02
1.3K0
Rust FFI 编程 - Rust导出共享库04
错误对于软件来说是不可避免的,错误处理是保证程序健壮性的前提,编程语言一般都会有一些机制来处理出现错误的情况,大致分为两种:抛出异常和作为值返回。
MikeLoveRust
2020/08/04
6390
2023学习日志
可以显式在代码中调用panic!宏,程序在执行到该语句时将报错并退出程序,而通过设置RUST_BACKTRACE环境变量,可以在panic!报错时输出当时的程序调用栈,便于debug。
TomoriNao
2023/07/06
1680
Rust语法之多线程(Tokio)
该示例代码创建了一个包含 9 个元素的 Vec,然后使用 Arc 和 Mutex 包装了该 Vec。接着,我们创建了 3 个线程,每个线程负责修改 Vec 的三分之一元素的值。在每个线程的执行体中,我们使用 Mutex 来获取 Vec 的写锁,并修改 Vec 中的元素。最后,我们等待所有线程完成,并输出修改后的 Vec。
码客说
2023/04/17
2K0
一次Rust重写基础软件的实践(三)
受到2022年“谷歌使用Rust重写Android系统且所有Rust代码的内存安全漏洞为零” [1] 的启发,最近笔者怀着浓厚的兴趣也顺应Rust 的潮流,尝试着将一款C语言开发的基础软件转化为 Rust 语言。本文的主要目的是通过记录此次转化过程中遇到的比较常见且有意思的问题以及解决此问题的方法与大家一起做相关的技术交流和讨论。
MikeLoveRust
2024/01/30
2290
一次Rust重写基础软件的实践(三)
初识Rust
虽然Rust工作上不一定用到,目前很难靠这个吃饭。但因为下面几个原因,有必要了解下Rust:
后端云
2022/11/25
5590
Rust vs C++:2024,谁更懂错误处理?
周五中午,在国内某科技巨头熙熙攘攘的员工餐厅,贾克强半开玩笑地戳了戳坐在隔壁的席双嘉,眼神中满是戏谑。
程序员吾真本
2024/04/03
5510
Rust vs C++:2024,谁更懂错误处理?
相关推荐
Rust学习笔记之错误处理
更多 >
LV.1
这个人很懒,什么都没有留下~
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验