Kesalahan yang Dapat Dipulihkan dengan Result
Kebanyakan kesalahan tidak serius sehingga memerlukan program untuk berhenti sepenuhnya. Terkadang, ketika sebuah fungsi gagal, itu karena alasan yang dapat Anda terjemahkan dan tanggapi dengan mudah. Sebagai contoh, jika Anda mencoba untuk menambahkan dua bilangan bulat besar dan operasinya melebihi kapasitas maksimum yang dapat direpresentasikan sehingga terjadi overflow, Anda mungkin ingin mengembalikan kesalahan atau hasil yang dibungkus daripada menyebabkan perilaku yang tidak terdefinisi atau menghentikan proses.
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,
}
Enum Result<T, E>
memiliki dua tipe generik, T
dan E
, dan dua variasi: Ok
yang menyimpan nilai tipe T
dan Err
yang menyimpan nilai tipe E
. Definisi ini membuatnya nyaman untuk menggunakan enum Result
di mana pun kita memiliki operasi yang mungkin berhasil (dengan mengembalikan nilai tipe T
) atau gagal (dengan mengembalikan nilai tipe E
).
ResultTrait
Trait ResultTrait
menyediakan metode-metode untuk bekerja dengan enum Result<T, E>
, seperti membuka nilai-nilai, memeriksa apakah Result
adalah Ok
atau Err
, dan melakukan panic dengan pesan kustom. Implementasi ResultTraitImpl
mendefinisikan logika dari metode-metode ini.
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;
}
Metode expect
dan unwrap
mirip dalam hal keduanya mencoba mengekstrak nilai tipe T
dari sebuah Result<T, E>
saat berada dalam varian Ok
. Jika Result
adalah Ok(x)
, kedua metode mengembalikan nilai x
. Namun, perbedaan utama antara kedua metode tersebut terletak pada perilaku mereka saat Result
berada dalam varian Err
. Metode expect
memungkinkan Anda untuk memberikan pesan kesalahan kustom (sebagai nilai felt252
) yang akan digunakan saat terjadi panic, memberikan lebih banyak kontrol dan konteks atas panic tersebut. Di sisi lain, metode unwrap
akan panic dengan pesan kesalahan default, memberikan informasi yang lebih sedikit tentang penyebab dari 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.
Akhirnya, metode is_ok
dan is_err
adalah fungsi utilitas yang disediakan oleh trait ResultTrait
untuk memeriksa varian dari nilai enumerasi Result
.
is_ok
mengambil gambaran (snapshot) dari nilaiResult<T, E>
dan mengembalikantrue
jikaResult
merupakan varianOk
, yang berarti operasi berhasil. JikaResult
merupakan varianErr
, maka mengembalikanfalse
.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.
Anda dapat menemukan implementasi dari ResultTrait
di sini.
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.
Sekarang, kita dapat menggunakan fungsi ini di tempat lain. Misalnya:
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')
.
Dua kasus uji kita adalah:
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::*;
#[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
Operator terakhir yang akan kita bahas adalah operator ?
. Operator ?
digunakan untuk penanganan kesalahan yang lebih idiomatik dan ringkas. Ketika Anda menggunakan operator ?
pada tipe Result
atau Option
, itu akan melakukan hal berikut:
- Jika nilainya adalah
Result::Ok(x)
atauOption::Some(x)
, maka akan mengembalikan nilai dalamnya, yaitux
, secara langsung. - Jika nilainya adalah
Result::Err(e)
atauOption::None
, maka akan menyebarkan kesalahan atauNone
dengan segera mengembalikan dari fungsi tersebut.
Operator ?
berguna ketika Anda ingin menangani kesalahan secara implisit dan membiarkan fungsi pemanggil menanganinya.
Berikut adalah contohnya:
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.
Dan dengan sedikit kasus uji:
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::*;
#[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
.
Ringkasan
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.
Untuk menangani kesalahan yang dapat dipulihkan, sebuah fungsi dapat mengembalikan tipe Result
dan menggunakan pola pencocokan untuk menangani keberhasilan atau kegagalan suatu operasi. Operator ?
dapat digunakan untuk menangani kesalahan secara implisit dengan menyebarkan kesalahan atau membuka nilai yang berhasil. Ini memungkinkan penanganan kesalahan yang lebih ringkas dan jelas, di mana pemanggil bertanggung jawab untuk mengelola kesalahan yang dihasilkan oleh fungsi yang dipanggil.