Recoverable Errors with Result
大多数错误并没有严重到需要程序完全停止的程度。有时,当一个函数失败时,它的原因是你可以很容易地解释和应对的。例如,如果你试图将两个大的整数相加,而操作溢出,因为总和超过了最大的可表示值,你可能想返回一个错误或一个包装好的结果,而不是引起未定义行为或终止程序。
The Result
Enum
Recall from Generic data types section in Chapter 8 that the Result
enum is defined as having two variants, Ok
and Err
, as follows:
enum Result<T, E> {
Ok: T,
Err: E,
}
Result<T, E>
枚举有两个泛型类型,T
和E
,以及两个成员:Ok
,存放T
类型的值,Err
,存放E
类型的值。这个定义使得我们可以在任何地方使用Result
枚举,该操作可能成功(返回T
类型的值)或失败(返回E
类型的值)。
The ResultTrait
ResultTrait
trait提供了处理Result<T, E>
枚举的方法,例如解包值,检查Result
是Ok
还是Err
,以及用自定义的消息进行panic。ResultTraitImpl
实现定义了这些方法的逻辑。
trait ResultTrait<T, E> {
fn expect<+Drop<E>>(self: Result<T, E>, err: felt252) -> T;
fn unwrap<+Drop<E>>(self: Result<T, E>) -> T;
fn expect_err<+Drop<T>>(self: Result<T, E>, err: felt252) -> E;
fn unwrap_err<+Drop<T>>(self: Result<T, E>) -> E;
fn is_ok(self: @Result<T, E>) -> bool;
fn is_err(self: @Result<T, E>) -> bool;
}
expect
和unwrap
方法类似,它们都试图从Result<T, E>
中提取T
类型的值,当它处于Ok
变体时。如果Result
是 Ok(x)
,两个方法都返回值 "x"。然而,这两个方法的关键区别在于当Result
是Err
变量时的行为。expect
方法允许你提供一个自定义的错误信息(作为felt252
值)在panic时使用,从而让你获取更多对panic相关的控制和上下文。另一方面,unwrap
方法用一个默认的错误信息进行panic,提供的关于panic原因的信息较少。
The expect_err
and unwrap_err
methods have the exact opposite behavior. If the Result
is Err(x)
, both methods return the value x
. However, the key difference between the two methods is in case of Result::Ok()
. The expect_err
method allows you to provide a custom error message (as a felt252
value) that will be used when panicking, giving you more control and context over the panic. On the other hand, the unwrap_err
method panics with a default error message, providing less information about the cause of the panic.
A careful reader may have noticed the <+Drop<T>>
and <+Drop<E>>
in the first four methods signatures. This syntax represents generic type constraints in the Cairo language, as seen in the previous chapter. These constraints indicate that the associated functions require an implementation of the Drop
trait for the generic types T
and E
, respectively.
最后,is_ok
和is_err
方法是ResultTrait
trait提供的实用函数,用于检查Result
枚举值的成员。
is_ok
获取一个Result<T, E>
值的快照,如果Result
是Ok
成员,则返回true
,意味着操作成功。如果Result
是Err
成员,则返回false
。is_err
takes a snapshot of aResult<T, E>
value and returnstrue
if theResult
is theErr
variant, meaning the operation encountered an error. If theResult
is theOk
variant, it returnsfalse
.
These methods are helpful when you want to check the success or failure of an operation without consuming the Result
value, allowing you to perform additional operations or make decisions based on the variant without unwrapping it.
你可以在这里找到 ResultTrait
的实现。
It is always easier to understand with examples. Have a look at this function signature:
fn u128_overflowing_add(a: u128, b: u128) -> Result<u128, u128>;
It takes two u128
integers, a
and b
, and returns a Result<u128, u128>
where the Ok
variant holds the sum if the addition does not overflow, and the Err
variant holds the overflowed value if the addition does overflow.
现在,我们可以在其他地方使用这个函数。比如说:
fn u128_checked_add(a: u128, b: u128) -> Option<u128> {
match u128_overflowing_add(a, b) {
Result::Ok(r) => Option::Some(r),
Result::Err(r) => Option::None,
}
}
Here, it accepts two u128
integers, a
and b
, and returns an Option<u128>
. It uses the Result
returned by u128_overflowing_add
to determine the success or failure of the addition operation. The match
expression checks the Result
from u128_overflowing_add
. If the result is Ok(r)
, it returns Option::Some(r)
containing the sum. If the result is Err(r)
, it returns Option::None
to indicate that the operation has failed due to overflow. The function does not panic in case of an overflow.
Let's take another example:
fn parse_u8(s: felt252) -> Result<u8, felt252> {
match s.try_into() {
Option::Some(value) => Result::Ok(value),
Option::None => Result::Err('Invalid integer'),
}
}
In this example, the parse_u8
function takes a felt252
and tries to convert it into a u8
integer using the try_into
method. If successful, it returns Result::Ok(value)
, otherwise it returns Result::Err('Invalid integer')
.
我们的两个测试案例是:
fn parse_u8(s: felt252) -> Result<u8, felt252> {
match s.try_into() {
Option::Some(value) => Result::Ok(value),
Option::None => Result::Err('Invalid integer'),
}
}
#[cfg(test)]
mod tests {
use super::parse_u8;
#[test]
fn test_felt252_to_u8() {
let number: felt252 = 5;
// should not panic
let res = parse_u8(number).unwrap();
}
#[test]
#[should_panic]
fn test_felt252_to_u8_panic() {
let number: felt252 = 256;
// should panic
let res = parse_u8(number).unwrap();
}
}
Don't worry about the #[cfg(test)]
attribute for now. We'll explain in more detail its meaning in the next Testing Cairo Programs chapter.
#[test]
attribute means the function is a test function, and #[should_panic]
attribute means this test will pass if the test execution panics.
The first one tests a valid conversion from felt252
to u8
, expecting the unwrap
method not to panic. The second test function attempts to convert a value that is out of the u8
range, expecting the unwrap
method to panic with the error message Invalid integer
.
The ?
Operator
我们要谈的最后一个操作符是?
操作符。?
运算符用于更成文和简明的错误处理。当你在 Result
或 Option
类型上使用?
运算符时,它将做以下事情:
- If the value is
Result::Ok(x)
orOption::Some(x)
, it will return the inner valuex
directly. - If the value is
Result::Err(e)
orOption::None
, it will propagate the error orNone
by immediately returning from the function.
当你想隐式处理错误并让调用函数处理它们时,?
操作符很有用。
下面是一个例子:
fn do_something_with_parse_u8(input: felt252) -> Result<u8, felt252> {
let input_to_u8: u8 = parse_u8(input)?;
// DO SOMETHING
let res = input_to_u8 - 1;
Result::Ok(res)
}
We can see that do_something_with_parse_u8
function takes a felt252
value as input and calls parse_u8
function. The ?
operator is used to propagate the error, if any, or unwrap the successful value.
这里还有一个小的测试案例:
fn parse_u8(s: felt252) -> Result<u8, felt252> {
match s.try_into() {
Option::Some(value) => Result::Ok(value),
Option::None => Result::Err('Invalid integer'),
}
}
fn do_something_with_parse_u8(input: felt252) -> Result<u8, felt252> {
let input_to_u8: u8 = parse_u8(input)?;
// DO SOMETHING
let res = input_to_u8 - 1;
Result::Ok(res)
}
#[cfg(test)]
mod tests {
use super::do_something_with_parse_u8;
#[test]
fn test_function_2() {
let number: felt252 = 258;
match do_something_with_parse_u8(number) {
Result::Ok(value) => println!("Result: {}", value),
Result::Err(e) => println!("Error: {}", e),
}
}
}
The console will print the error Invalid Integer
.
Summary
We saw that recoverable errors can be handled in Cairo using the Result
enum, which has two variants: Ok
and Err
. The Result<T, E>
enum is generic, with types T
and E
representing the successful and error values, respectively. The ResultTrait
provides methods for working with Result<T, E>
, such as unwrapping values, checking if the result is Ok
or Err
, and panicking with custom messages.
为了处理可恢复的错误,一个函数可以返回一个Result
类型,并使用模式匹配来处理操作的成功或失败。?
操作符可用于通过传播错误或解包成功的值来隐含地处理错误。这使得错误处理更加简洁明了,调用者负责管理由被调用函数引发的错误。