Rust中错误处理是如何进行的?

简介: Rust中错误处理是如何进行的?

Rust中错误处理是如何进行的?

在其他语言中,对于错误的处理是通过“异常”这一操作进行统一处理。而在Rust中,对这两种错误提供了不同的解决方法:可恢复错误和不可恢复错误

Panic!和不可恢复的错误

Rust的错误处理有何不同?

Rust有极高的可靠性,这也延伸到了错误处理的领域:

  • 大部分情况下,我们在编译时被提示错误,并进行处理

Rust中的错误的分类:

  • 可恢复:例如文件未找到,会将错误信息返回给用户,并让其再次尝试寻找这个文件
  • 不可恢复:也称为bug,例如访问的索引超出范围

在其他语言中,没有对这两种错误进行区分,而是通过“异常”这一操作进行统一处理。而在Rust中,对这两种错误提供了不同的解决方法:

  • 可恢复错误:Result\<T,E>
  • 不可恢复:panic!宏(程序会立即终止)

不可恢复的错误与panic!

当panic!宏执行,会经历以下步骤:

  • 你的程序会打印一个错误信息
  • 展开(unwind)、清理调用栈(Stack)
  • 退出程序

对于使用panic!,展开或中止(abort)调用栈

默认情况下,当panic发生:

  • 程序展开调用栈(工作量大):
    • Rust沿着调用栈往回走
    • 清理每个遇到的函数中的数据
  • 或者立即中止(abort)调用栈:
    • 不进行清理,直接停止程序
    • 内存需要OS进行清理

如果我们想让二进制文件更小,把设置从“展开”改为“中止”:

  • 在Cargo.toml中适当的profile部分设置为:panic='abort'
[package]
name = "panic"
version = "0.1.0"
edition = "2021"

[dependencies]

[profile.release]
panic = 'abort'

我们在src/main.rs中使用panic!宏:

fn main() {
    panic!("crash and burn");
}

使用cargo run运行,会输出:hread 'main' panicked at 'crash and burn', src\main.rs:2:5

使用panic!产生的回溯信息

我们再看一个例子:

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

运行会报错:thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 88', src\main.rs:5:5。

我们也可以更改RUST_BACKTRACE来得到更详细的信息

通过上面的例子,我们知道panic!可能出现在:

  • 我们写的代码中
  • 我们所依赖的代码中
我们可通过调用panic!的函数的回溯信息来定位引起问题的代码(通过设置环境变量RUST_BACKTRACE得到回溯信息)

Result枚举与可恢复的错误

Result枚举

在程序中,大部分错误都没严重到要中止程序的程度。对于这种错误,我们可以使用Result枚举来处理,下面是Result枚举的定义:

enum Result<T,E>{
    OK(T),
    Err(E),
}
  • T:操作成功的情况下,Ok变体里返回的数据的类型
  • E:操作失败的情况下,Err变体里返回的错误的类型

我们尝试打开一个文件,这个文件有可能不存在

let file = File::open("hello.text");//file: Result<File, Error>

处理Result的一种方式:match表达式

和Option枚举一样,Result及其变体也是由prelude带入作用域

use std::fs::File;

fn main() {
    let f = File::open("hello.text");
    match f {
        Ok(file) => file,
        Err(error) => panic!("{:?}", error),
    };
}

报错:thread 'main' panicked at 'Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }', src\main.rs:7:23

匹配不同的错误

use std::{fs::File, io::ErrorKind};

fn main() {
    let f = File::open("hello.text");
    match f {
        Ok(file) => file,
        Err(error) => match error.kind() {
            ErrorKind::NotFound => match File::create("hello.text") {
                Ok(file_create) => file_create,
                Err(e) => panic!("Error create a file:{:?}", e),
            },
            other_error => panic!("Error opening the file:{:?}", other_error),//other_error为自定义变量名
        },
    };
}

上面的例子中使用了很多的match,当然match很有用,但同时也很原始。

我们在后面可以使用闭包(closure)。Result\<T,E>有很多方法:

  • 它们接收闭包作为参数
  • 使用match实现
  • 使用这些方法会让代码更简洁

下面的代码会在后面了解闭包后才能明白

use std::{fs::File, io::ErrorKind};

fn main() {
    let f = File::open("hello.text").unwrap_or_else(|error| {
        if error.kind() == ErrorKind::NotFound {
            File::create("hello.text").unwrap_or_else(|error| {
                panic!("Error creating file:{:?}", error);
            })
        } else {
            panic!("Error opening file:{:?}", error);
        }
    });
}

unwrap

unwrap:match表达式的一个快捷方法:

  • 如果Result结果是Ok,返回Ok里面的值
  • 如果Result结果是Err,调用panic!宏
let f = File::open("hello.text").unwrap();

上面的代码等同于:

use std::fs::File;

fn main() {
    let f = File::open("hello.text");
    match f {
        Ok(file) => file,
        Err(error) => panic!("{:?}", error),
    };
}

但是这个方法有个缺点:不能自定义panic!的内容

所以rust还给我们提供了expect方法

expect

expect:和unwrap类似,但可指定错误信息

let f = File::open("hello.text").expect("无法打开文件:hello.txt");

thread 'main' panicked at '无法打开文件:hello.txt: Os { code: 2, kind: NotFound, message: "系统找不到指定的文件。" }', src\main.rs:4:38

传播错误

之前我们介绍的是在函数中处理错误,现在我们要将错误返回给调用者

根据前面的例子,我们可能会写出这样的代码:

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

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

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };
    let mut s = String::new();
    //返回match
    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

fn main() {
    let result = read_file(); //result: Result<String, Error>
}

在Rust中,传播错误是十分常见的。所以说它还提供了?运算符

?运算符

?运算符是一种传播错误的一种快捷方式

我们使用运算符来简化上面的例子:

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

fn read_file() -> Result<String, io::Error> {
    let mut f = File::create("hello.txt")?;

    let mut s = String::new();
    f.read_to_string(&mut s);
    Ok(s)
}

fn main() {
    let result = read_file(); //result: Result<String, Error>
}

如果Result是Ok:Ok中的值就是表达式的结果,然后继续执行程序

如果Result是Err:Err就是整个函数的返回值,就像使用了return

?与from函数

Trait std::convert::From上的from函数:

  • 用于错误之间的转换

?所应用的错误,会隐式的被from函数处理,当?调用from函数时:它所接收的错误类型会被转化为当前函数返回类型所定义的错误类型

我们还可以使用链式调用来优化:

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

?运算符只能用于返回Result的函数

?运算符与main函数

main函数的返回类型为:()

但它的返回类型也可以是:Result\<T,E>

use std::{error::Error, fs::File};

fn main() -> Result<(), Box<dyn Error>> {
    let result = File::open("xx.txt")?;
    Ok(())
}

其中,Box<dyn Error>是trait对象,我们可以简单理解为:“任何可能的错误类型”

相关文章
|
4月前
|
Rust 自然语言处理 算法
【Rust 中的错误处理:掌握 Option、Result、expect、unwrap 和 ? 运算符】Error Handling in Rust
【Rust 中的错误处理:掌握 Option、Result、expect、unwrap 和 ? 运算符】Error Handling in Rust
165 0
|
4月前
|
Rust 安全 开发者
Rust中的错误处理策略:Result类型与Panic
Rust语言以其强大的内存安全和并发编程能力而著称。在Rust中,错误处理是一个核心概念,其独特的处理方式体现在Result类型和Panic机制上。本文将深入探讨这两种错误处理策略在Rust中的应用,以及它们如何帮助开发者构建更加健壮和安全的程序。
|
4月前
|
Rust
【一起学Rust · 项目实战】命令行IO项目minigrep——重构优化模块和错误处理
【一起学Rust · 项目实战】命令行IO项目minigrep——重构优化模块和错误处理
57 0
|
4月前
|
Rust 开发者
rust 笔记 高级错误处理(二)
rust 笔记 高级错误处理
120 0
|
4月前
|
Rust
rust 笔记 高级错误处理(一)
rust 笔记 高级错误处理
76 0
|
4月前
|
Rust
Rust错误处理
Rust错误处理
82 0
|
Rust 安全 开发者
Rust学习笔记之错误处理
panic! 与不可恢复的错误 推荐阅读指数 ⭐️⭐️⭐️⭐️ Result 与可恢复的错误 推荐阅读指数 ⭐️⭐️⭐️⭐️
Rust学习笔记之错误处理
|
21天前
|
Rust 安全 Go
揭秘Rust语言:为何它能让你在编程江湖中,既安全驰骋又高效超车,颠覆你的编程世界观!
【8月更文挑战第31天】Rust 是一门新兴的系统级编程语言,以其卓越的安全性、高性能和强大的并发能力著称。它通过独特的所有权和借用检查机制解决了内存安全问题,使开发者既能享受 C/C++ 的性能,又能避免常见的内存错误。Rust 支持零成本抽象,确保高级抽象不牺牲性能,同时提供模块化和并发编程支持,适用于系统应用、嵌入式设备及网络服务等多种场景。从简单的 “Hello World” 程序到复杂的系统开发,Rust 正逐渐成为现代软件开发的热门选择。
37 1
|
1月前
|
Rust 安全 编译器
初探 Rust 语言与环境搭建
Rust 是一门始于2006年的系统编程语言,由Mozilla研究员Graydon Hoare发起,旨在确保内存安全而不牺牲性能。通过所有权、借用和生命周期机制,Rust避免了空指针和数据竞争等问题,简化了并发编程。相较于C/C++,Rust在编译时预防内存错误,提供类似C++的语法和更高的安全性。Rust适用于系统编程、WebAssembly、嵌入式系统和工具开发等领域。其生态系统包括Cargo包管理器和活跃社区。学习资源如&quot;The Book&quot;和&quot;Rust by Example&quot;帮助新手入门。安装Rust可通过Rustup进行,支持跨平台操作。
102 2
初探 Rust 语言与环境搭建
|
21天前
|
Rust 安全 程序员
Rust 语言的防错机制太惊人了!安全编码从此不再是难题,快来一探究竟!
【8月更文挑战第31天】《安全编码原则:Rust 语言中的防错机制》探讨了代码安全的重要性,并详细介绍了Rust语言如何通过内存安全模型、所有权与借用规则等特性,在编译阶段检测并阻止潜在错误,如缓冲区溢出和悬空指针。文章还讨论了类型安全、边界检查等其他安全特性,并提出了遵循不可变引用、避免裸指针及充分测试等实用编码原则,以进一步提升代码质量和安全性。随着Rust在软件开发中的应用日益广泛,掌握其安全编码原则变得尤为重要。
31 0