前言
上一篇文章聊到 Rust 的错误处理机制,以及和 Java 的简单比较,现在就来聊一下如何在
Rust 自定义错误,以及引入 error_chain
这个库来优雅地进行错误处理。还有,少不了
用 Java 来做对比咯:)
Java 自定义异常
前文简单提到 Java 的错误和异常但是继承自一个 Throwable
的父类,既然异常是继承
自异常父类的,我们自定义异常的时候,也可以模仿 JDK, 继承一个异常类:
public class MyException extends Exception {
public MyException(String message) {
super(message);
}
}
这样就定义了属于自己的异常. 只需要继承 Exception
,然后调用父类的构造方法。不过
对于那些复杂的项目,这样的例子未免过于简单。现在就来看一个我项目的中的一个异常类:
public final class MyError extends RuntimeException {
/**
*
*/
private static final long serialVersionUID = 1L;
private static boolean isFillStack = true;
private Integer httpStatusCode;
private Integer code;
private String message;
private MyError(int httpStatusCode, int code, String message) {
super(message, null, isFillStack, isFillStack);
this.httpStatusCode = httpStatusCode;
this.code = code;
this.message = message;
}
public MyError(MyErrorCode myErrorCode, Object... messageArgs) {
this(myErrorCode.getHttpStatusCode(), myErrorCode.getErrorCode(),
MessageFormat.format(myErrorCode.getMessagePattern(), messageArgs));
}
public static MyError throwError(MyErrorCode myErrorCode, Object... messageArgs) {
throw new MyError(myErrorCode, messageArgs);
}
public static MyError internalServerError(String logId) {
throw new MyError(MyErrorCode.INTERNAL_SERVER_ERROR, logId);
}
public static MyError DataError(String logId) {
throw new MyError(MyErrorCode.DATA_ERROR, logId);
}
public static MyError BadParameterError(String logId) {
throw new MyError(MyErrorCode.BAD_PARAMETER_ERROR, logId);
}
}
这是我去掉了多余方法和变量的简化版,但是也足以一叶知秋了。 MyError
这个异常类是
继承于 RuntimeException
的,并调用了 RuntimeException
的构造方法。因为我的项目
是 WEB 服务的业务层,要处理大量的逻辑,难免会出现异常,比如说可能调用方调用接口
的时候,入参不符合规范,我就抛出一个经过包装的 BadParameterError
异常,对于接
口调用方,这样会比一个单纯的 400 错误要友好,其他的异常也是同理。
Rust 自定义错误
对于习惯了 OOP 编程的同学来说,Java 的异常是很容易理解,但是回到 Rust 身上,Rust
是没有父类一说的,显然,Rust 是没可能套用 Java 的自定义异常的方式的。Rust 用的是
trait
, trait
就有点类似 Java 的 interface
(只是类似,不是等同!). 按照 Rust
的规范,Rust 允许开发者定义自己的错误,设计良好的错误应该包含以下的特性:
- 使用相同的类型(type)来表示不同的错误
- 错误中包含对用户友好的提示(我也在上面提到的)
- 能便捷地与其他类型比较,例如:
- Good:
Err(EmptyVec)
- Bad:
Err("Please use a vector with at least one element".to_owned())
- Good:
- 包含与错误相关的信息,例如:
- Good:
Err(BadChar(c, position))
- Bad:
Err("+ cannot be used here".to_owned())
- 可以很方便地与其他错误结合
use std::error;
use std::fmt;
use std::num::ParseIntError;
type Result<T> = std::result::Result<T, DoubleError>;
#[derive(Debug, Clone)]
// 自定义错误类型。
struct DoubleError;
// 不同的错误需要展示的信息也不一样,这个就要视情况而定,因为 DoubleError 没有定义额外的字段来保存错误信息
// 所以就现在就简单打印错误信息
impl fmt::Display for DoubleError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "invalid first item to double")
}
}
// 实现 error::Error 这个 trait, 对DoubleError 进行包装
impl error::Error for DoubleError {
fn description(&self) -> &str {
"invalid first item to double"
}
fn cause(&self) -> Option<&error::Error> {
// Generic error, underlying cause isn't tracked.
None
}
}
fn double_first(vec: Vec<&str>) -> Result<i32> {
vec.first()
// Change the error to our new type.
.ok_or(DoubleError)
.and_then(|s| s.parse::<i32>()
// Update to the new error type here also.
.map_err(|_| DoubleError)
.map(|i| 2 * i))
}
fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}
fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];
print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}
这段代码的运行结果如下:
The first doubled is 84
Error: invalid first item to double
Error: invalid first item to double
error_chain
虽说 Rust 自定义错误很灵活和方便,但是如果每次定义异常都需要实现 Display
和Error
,
未免过于繁琐,现在来介绍error_chain 这个类库。error_chain
是由 Rust 项目组的 leader--BrianAnderson编写的异常处理库,可以让你更舒心简单不粗暴地定义错误。
error_chain示例
以上面的 DoubleError
为例,并改写 error_chain 的官方例
子
以实现相同的效果,代码如下:
// `error_chain!` 的递归深度
#![recursion_limit = "1024"]
//引出 error_chain 和相应的宏
#[macro_use]
extern crate error_chain;
//将跟错误有关的内容放入 errors module, 其他需要用到这个错误module 的模块就通过
// use errors::* 来引入所有内容
mod errors {
// Create the Error, ErrorKind, ResultExt, and Result types
error_chain! {
errors{Double{
description("invalid first item to double")
display("invalid first item to double")
}}
}
}
use errors::*;
pub type Result<T> = ::std::result::Result<T, ErrorKind>;
fn main() {
let numbers = vec!["42", "93", "18"];
let empty = vec![];
let strings = vec!["tofu", "93", "18"];
print(double_first(numbers));
print(double_first(empty));
print(double_first(strings));
}
fn double_first(vec: Vec<&str>) -> Result<i32> {
vec.first()
// Change the error to our new type.
.ok_or(ErrorKind::Double)
.and_then(|s| s.parse::<i32>()
// Update to the new error type here also.
.map_err(|_| ErrorKind::Double)
.map(|i| 2 * i))
}
fn print(result: Result<i32>) {
match result {
Ok(n) => println!("The first doubled is {}", n),
Err(e) => println!("Error: {}", e),
}
}
运行这代码可以得到和上面小节同样的输出。
小结
刚刚的例子只是 error_chain
小试了一波牛刀,如果想要了解更多关于 Rust 异常处理
的细节,就需要看看 Rust 的文档咯