错误管理是Rust编程的重要组成部分,允许开发人员优雅地处理不可预见的事件。Rust有各种错误管理功能,包括用于故意程序崩溃的panic宏,用于描述操作结果的Result枚举,以及简单错误传播的操作符。使用这些方法和定制的错误类型,开发人员可以创建强大而可靠的软件,以正确地管理错误。了解错误处理系统的Rust开发人员可以构建更安全和更可维护的代码,从而提高其程序的整体质量和可靠性。
Introduction 介绍
Error handling basically means how to handle certain situations which are not optimal for your program to work in, there are some gracefully handleable errors
错误处理基本上意味着如何处理某些情况下,这是不是最佳的为您的程序的工作,有一些优雅的可删除的错误
If the error cannot be handled/recovered from we can call the panic
macro and crash our program intentionally, We will also take a look at back tracing…
如果错误不能被处理/恢复,我们可以调用 panic
宏并故意崩溃我们的程序,我们还将看看回溯.
Panic Macro Panic宏
The panic macro is used when we want our program to crash on purpose, If an error cannot be handled gracefully or the program cannot recover from the error, we can call the panic macro by using the following syntax:
panic宏用于当我们希望我们的程序崩溃时,如果错误不能被优雅地处理或程序不能从错误中恢复,我们可以使用以下语法调用panic宏:
panic!(error_message: &str);
Example: 范例:
fn main(){panic!("This is a test panic");
}
Output: 输出量:
thread 'main' panicked at src/main.rs:2:3:
This is a test panic
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
The panic message shows we can run the program with RUST_BACKTRACE=1
environment variable to get a backtrace
panic消息显示我们可以使用 RUST_BACKTRACE=1
环境变量运行程序以获得回溯
What is a backtrace? 什么是回溯?
A backtrace is a trace of all the functions called before the code panicked let’s take a look at that with a slightly accurate example:
回溯是在代码死机之前调用的所有函数的跟踪,让我们用一个稍微准确的例子来看看:
fn main(){a();
}fn a(){b();
}
fn b(){panic!("Function panicked");
}
Output: 输出量:
thread 'main' panicked at src/main.rs:9:5:
Function panicked
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Output with RUST_BACKTRACE=1
: 输出 RUST_BACKTRACE=1
:
thread 'main' panicked at src/main.rs:9:5:
Function panicked
stack backtrace:0: rust_begin_unwindat /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/std/src/panicking.rs:645:51: core::panicking::panic_fmtat /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/panicking.rs:72:142: error_handling::bat ./src/main.rs:9:53: error_handling::aat ./src/main.rs:6:54: error_handling::mainat ./src/main.rs:2:55: core::ops::function::FnOnce::call_onceat /rustc/07dca489ac2d933c78d3c5158e3f43beefeb02ce/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
In this example we can see that, main()
calls a function a()
, then a()
calls b()
which crashes the program and we can clearly follow through what happens in the code using the backtrace.
在这个例子中,我们可以看到, main()
调用了一个函数 a()
,然后 a()
调用了 b()
,这使程序崩溃,我们可以清楚地通过使用回溯跟踪来跟踪代码中发生的事情。
Result enum 结果枚举
The Result
enum in Rust simplifies error handling by providing a way to represent the success or failure of operations, For example opening a file may fail if the file does not exist and that would result in an error.
Rust中的 Result
枚举通过提供一种表示操作成功或失败的方法来简化错误处理,例如,如果文件不存在,则打开文件可能会失败,这将导致错误。
The result enum looks something like this.
结果枚举看起来像这样。
match Result(Ok(arguments) => {},Err(error) => {},
);
Let’s take a better look in an example…
让我们更好地看看一个例子.
use std::fs::File;
use std::io::prelude::*;fn main() {// Attempt to open a filematch File::create("example.txt") {Ok(mut file) => {// File created successfully, write content to itif let Err(e) = file.write_all(b"Hello, World!") {println!("Failed to write to file: {}", e);} else {println!("Data successfully written to file.");}}Err(e) => {// Failed to create file, handle the errorprintln!("Failed to create file: {}", e);}}
}
- The code attempts to create a file named “example.txt” using
File::create()
.
代码尝试使用File::create()
创建名为“example.txt”的文件。 - It matches the result of the file creation operation using a
match
statement.
它使用match
语句匹配文件创建操作的结果。 - If the file is successfully created (
Ok(mut file)
), it proceeds to write "Hello, World!" to the file.
如果文件成功创建(Ok(mut file)
),它会继续写入“Hello,World!”的文件。 - If writing to the file fails (
Err(e)
), it prints an error message.
如果写入文件失败(Err(e)
),它将打印一条错误消息。 - If creating the file fails (
Err(e)
), it prints an error message, indicating the reason for the failure.
如果创建文件失败(Err(e)
),它将打印一条错误消息,指出失败的原因。
Output : 输出量:
Data successfully written to file.
example.txt : example.txt:
Hello, World!
Instead of using the match
expression we could have also called the unwrap()
or expect()
method to do this as well.
除了使用 match
表达式,我们还可以调用 unwrap()
或 expect()
方法来完成这一任务。
The unwrap()
method is used to retrieve the value from a Result
enum, panicking if it encounters an Err
, while expect()
provides similar functionality but allows for a custom error message to be provided in case of failure.unwrap()
方法用于从 Result
枚举中检索值,如果遇到 Err
则会出现恐慌,而 expect()
提供了类似的功能,但允许在失败时提供自定义错误消息。
Error propagation 误差传播
In Rust, error propagation is accomplished by propagating errors up the call stack via the Result type, allowing for modular error handling while retaining abstraction levels. It encourages centralized error handling, allowing for uniform tactics throughout the program, and improves debugging by giving contextual information about problem causes. This strategy guarantees graceful failure handling and helps to build strong and maintainable software.
在Rust中,错误传播是通过Result类型将错误传播到调用堆栈来完成的,允许模块化错误处理,同时保留抽象级别。它鼓励集中的错误处理,允许在整个程序中使用统一的策略,并通过提供有关问题原因的上下文信息来改进调试。这种策略保证了优雅的故障处理,并有助于构建强大和可维护的软件。
Let’s take a look at an example
我们来看一个例子
use std::fs::File;
use std::io::{self, Write};fn write_to_file(data: &[u8]) -> io::Result<()> {let mut file = match File::create("example.txt") {Ok(file) => file,Err(e) => return Err(e),};match file.write_all(data) {Ok(_) => Ok(()),Err(e) => Err(e),}
}fn main() {let data = b"Hello, World!";match write_to_file(data) {Ok(()) => println!("Data successfully written to file."),Err(e) => println!("Failed to write to file: {}", e),}
}
- The
write_to_file
function attempts to create a file named "example.txt" and write data to it.write_to_file
函数尝试创建一个名为“example.txt”的文件并向其中写入数据。 - Inside
write_to_file
, amatch
statement is used to handle the result ofFile::create
. If the file creation is successful, the file handle is stored infile
. If it fails, the error is returned early.
在write_to_file
中,一个match
语句用于处理File::create
的结果。如果文件创建成功,则文件句柄存储在file
中。如果失败,则会提前返回错误。 - Another
match
statement is used to handle the result ofwrite_all
. If the write operation is successful,Ok(())
is returned. If it fails, the error is returned.
另一个match
语句用于处理write_all
的结果。如果写操作成功,则返回Ok(())
。如果失败,则返回错误。 - In the
main
function, the result ofwrite_to_file
is matched again to determine whether the operation was successful or not.
在main
函数中,再次匹配write_to_file
的结果以确定操作是否成功。
This code can be made shorter and easier to read using the ?
operator.
使用 ?
操作符可以使这段代码更短,更容易阅读。
The ?
operator in Rust is a shorthand for error propagation. It can be used within functions that return a Result
type, simplifying error handling by automatically unwrapping the Ok
variant and returning the contained value or propagating the Err
variant.
Rust中的 ?
操作符是错误传播的简写。它可以在返回 Result
类型的函数中使用,通过自动展开 Ok
变量并返回包含的值或传播 Err
变量来简化错误处理。
use std::fs::File;
use std::io::{self, Write};fn write_to_file(data: &[u8]) -> io::Result<()> {let mut file = File::create("example.txt")?;file.write_all(data)?;Ok(())
}fn main() -> io::Result<()> {let data = b"Hello, World!";write_to_file(data)?;println!("Data successfully written to file.");Ok(())
}
- The
write_to_file
function attempts to create a file named "example.txt" and write data to it.write_to_file
函数尝试创建一个名为“example.txt”的文件并向其中写入数据。 - The
?
operator is used after each potentially error-prone operation (File::create
andwrite_all
). If an error occurs at any step, it will be propagated up the call stack, and the function will return early with that error.?
操作符用于每个可能出错的操作(File::create
和write_all
)之后。如果在任何步骤中发生错误,它将在调用堆栈中向上传播,并且函数将提前返回该错误。 - In the
main
function, error propagation is also used, allowing any error fromwrite_to_file
to be handled gracefully, and if successful, it prints a success message.
在main
函数中,还使用了错误传播,允许优雅地处理来自write_to_file
的任何错误,如果成功,它会打印一条成功消息。
Overall, the ?
operator streamlines error handling in Rust code by reducing boilerplate and improving readability.
总的来说, ?
操作符通过减少样板和提高可读性来简化Rust代码中的错误处理。
Custom types for error handling
用于错误处理的自定义类型
We created a guessing game in the start of this series, if you haven’t read that, click here
我们在本系列开始时创建了一个猜谜游戏,如果您还没有阅读,请单击此处
In that we were simply taking the number from the user without any form of validation, although our guess was in the range 1..=100
. The user could input any number so let’s fix that?
因为我们只是简单地从用户那里获取数字,而没有任何形式的验证,尽管我们的猜测是在 1..=100
范围内。用户可以输入任何数字,让我们来解决这个问题吧?
pub struct Guess{value: i32,
}impl Guess {pub fn new(value: i32) -> Guess{if value < 1 || value > 100 {panic!("Value must be between 1 to 100, got {}", value);}Guess{ value }}pub fn value(&self) -> i32{self.value}
}
Now we can use this to improve our guessing game, let’s integrate this in the code…
现在我们可以用它来改进我们的猜谜游戏,让我们把它集成到代码中。
use std::io;
use std::cmp::Ordering;
use rand::Rng;pub struct Guess{value: i32,
}impl Guess {pub fn new(value: i32) -> Guess{if value < 1 || value > 100 {panic!("Value must be between 1 to 100, got {}", value);}Guess{ value }}pub fn value(&self) -> i32{self.value}
}fn main() {println!("Guess the number!");let secret_number = rand::thread_rng().gen_range(1..=100);loop {println!("Please input your guess");let mut guess = String::new();io::stdin().read_line(&mut guess).expect("Failed to read line");let guess: i32 = match guess.trim().parse() {Ok(num) => num,Err(_) => continue,};let guess: Guess = Guess::new(guess);println!("Your guess: {}", guess.value);match guess.value().cmp(&secret_number) {Ordering::Less => println!("Too small!"),Ordering::Greater => println!("Too big!"),Ordering::Equal => {println!("You win!");break;},}}
}
Output: 输出量:
Guess the number!
Please input your guess
100
Your guess: 100
Too big!
Please input your guess
291
thread 'main' panicked at src/main.rs:12:7:
Value must be between 1 to 100, got 291