异常处理
在 .NET 中,异常是一种派生自 System.Exception
的类。如果一段代码发生了问题,那么异常就会被抛出。抛出的异常会沿着栈传递,直到应用程序处理了它,或者程序终止。
Rust 没有异常,但是区分了 可恢复 与 不可恢复 的错误。一个可恢复的错误代表着一个应当被报告的问题,但是程序为此继续运行。对于可能会因可恢复的错误而失败的操作,它的的结果是类型 Result<T,E>
,其中 E
是错误变体的类型。panic!
宏会在程序遇到不可恢复的错误时终止程序。不可恢复的错误总是意味着漏洞的存在。
自定义错误类型
在 .NET 中, 自定义异常从 Exception
类派生。关于 如何创建用户定义的异常 的文档提到了这个例子:
public class EmployeeListNotFoundException : Exception
{
public EmployeeListNotFoundException() { }
public EmployeeListNotFoundException(string message)
: base(message) { }
public EmployeeListNotFoundException(string message, Exception inner)
: base(message, inner) { }
}
在 Rust 中,可以通过实现 Error
trait 来实现错误值的基本要求。Rust 中最简短的用户定义错误实现是:
#[derive(Debug)]
pub struct EmployeeListNotFound;
impl std::fmt::Display for EmployeeListNotFound {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("Could not find employee list.")
}
}
impl std::error::Error for EmployeeListNotFound {}
.NET 中 Exception.InnerException
属性的等价是 Rust 中的 Error::source()
方法。然而,对 Error::source()
的实现并非必要的,通用(默认的)实现返回一个 None
。
引发异常
要在 C# 中引发异常,抛出一个异常的实例:
void ThrowIfNegative(int value)
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
}
对于 Rust 中的可恢复错误,方法应当返回一个 Ok
或 Err
变体:
fn error_if_negative(value: i32) -> Result<(), &'static str> {
if value < 0 {
Err("Specified argument was out of the range of valid values. (Parameter 'value')")
} else {
Ok(())
}
}
panic!
宏创建不可恢复的错误:
fn panic_if_negative(value: i32) {
if value < 0 {
panic!("Specified argument was out of the range of valid values. (Parameter 'value')")
}
}
错误传播
在 .NET 中,异常会沿着调用栈传递直到它们被处理或者程序终止。在 Rust 中,不可恢复的错误表现类似,不过通常不会处理它们。
然而,可恢复的错误需要被显式地传播和处理。它们的存在总是由 Rust 函数或方法的签名表示。在 C# 中,捕获异常允许你对错误的存在与否进行处理:
void Write()
{
try
{
File.WriteAllText("file.txt", "content");
}
catch (IOException)
{
Console.WriteLine("Writing to file failed.");
}
}
在 Rust 中,这大致相当于:
fn write() {
match std::fs::File::create("temp.txt")
.and_then(|mut file| std::io::Write::write_all(&mut file, b"content"))
{
Ok(_) => {}
Err(_) => println!("Writing to file failed."),
};
}
大多数情况下,可恢复错误更需要被传播而不是处理。这种情况下,方法签名需要与要传播的错误类型兼容。?
运算符 以一种符合人体工学的方式传播错误。
fn write() -> Result<(), std::io::Error> {
let mut file = std::fs::File::create("file.txt")?;
std::io::Write::write_all(&mut file, b"content")?;
Ok(())
}
备注:若要使用问号运算符来传播错误,错误实现必须 兼容,就像传播错误的简写:? 运算符中提到的。最一般的错误类型是错误的 trait 对象 Box<dyn Error>
。
堆栈跟踪
在 .NET 中抛出异常会导致运行时打印堆栈跟踪信息,这样可以借助额外的上下文来调试问题。
对于 Rust 中的可恢复错误,panic
backtrace 提供了类似行为。
stable Rust 中的可恢复错误还不支持 backtrace,不过它当前已有实验性支持,通过使用provide 方法。