Kesalahan yang Dapat Diperbaiki dengan Result
Sebagian besar kesalahan tidak cukup serius untuk memerlukan program berhenti sepenuhnya. Terkadang, ketika suatu fungsi gagal, itu disebabkan oleh suatu alasan yang dapat Anda interpretasikan dan tanggapi dengan mudah. Sebagai contoh, jika Anda mencoba menambahkan dua bilangan bulat besar dan operasinya melampaui batas nilai yang dapat direpresentasikan, Anda mungkin ingin mengembalikan kesalahan atau hasil yang dibungkus daripada menyebabkan perilaku yang tidak terdefinisi atau menghentikan proses.
Enum Result
Ingat dari “Tipe data generik” di Bab 8 bahwa enum Result
didefinisikan memiliki dua varian, Ok
dan Err
, sebagai berikut:
{{#include ../listings/ch10-error-handling/no_listing_07_result_enum/src/lib.cairo}}
Enum Result<T, E>
memiliki dua tipe generik, T
dan E
, dan dua varian: Ok
yang menyimpan nilai bertipe T
dan Err
yang menyimpan nilai bertipe E
. Definisi ini memudahkan penggunaan enum Result
di mana pun kita memiliki operasi yang mungkin berhasil (dengan mengembalikan nilai bertipe T
) atau gagal (dengan mengembalikan nilai bertipe E
).
Trait ResultTrait
Trait ResultTrait
menyediakan metode-metode untuk bekerja dengan enum Result<T, E>
, seperti membuka nilai, memeriksa apakah Result
adalah Ok
atau Err
, dan memicu panic dengan pesan kustom. Implementasi ResultTraitImpl
mendefinisikan logika dari metode-metode ini.
{{#include ../listings/ch10-error-handling/no_listing_08_result_trait/src/lib.cairo}}
Metode expect
dan unwrap
serupa dalam hal keduanya mencoba mengekstrak nilai bertipe T
dari Result<T, E>
ketika berada dalam varian Ok
. Jika Result
adalah Ok(x)
, keduanya mengembalikan nilai x
. Namun, perbedaan kunci antara kedua metode tersebut terletak pada perilaku mereka saat Result
berada dalam varian Err
. Metode expect
memungkinkan Anda menyediakan pesan kesalahan kustom (sebagai nilai felt252
) yang akan digunakan saat terjadi panic, memberi Anda lebih banyak kontrol dan konteks atas panic tersebut. Di sisi lain, metode unwrap
memicu panic dengan pesan kesalahan default, memberikan informasi yang lebih sedikit tentang penyebab panic.
Metode expect_err
dan unwrap_err
memiliki perilaku yang sama persis, tetapi kebalikannya. Jika Result
adalah Err(x)
, keduanya mengembalikan nilai x
. Namun, perbedaan kunci antara keduanya terletak pada kasus Result::Ok()
. Metode expect_err
memungkinkan Anda menyediakan pesan kesalahan kustom (sebagai nilai felt252
) yang akan digunakan saat terjadi panic, memberi Anda lebih banyak kontrol dan konteks atas panic tersebut. Di sisi lain, metode unwrap_err
memicu panic dengan pesan kesalahan default, memberikan informasi yang lebih sedikit tentang penyebab panic.
Seorang pembaca yang teliti mungkin telah memperhatikan <+Drop<T>>
dan <+Drop<E>>
dalam empat tanda tangan metode pertama. Sintaks ini mewakili kendala tipe generik dalam bahasa Cairo. Kendala ini menunjukkan bahwa fungsi terkait memerlukan implementasi dari trait Drop
untuk tipe generik T
dan E
masing-masing.
Terakhir, metode is_ok
dan is_err
adalah fungsi utilitas yang disediakan oleh trait ResultTrait
untuk memeriksa varian dari nilai enum Result
.
is_ok
mengambil cuplikan nilai Result<T, E>
dan mengembalikan true
jika Result
adalah varian Ok
, yang berarti operasi berhasil. Jika Result
adalah varian Err
, metode ini mengembalikan false
.
is_err
mengambil referensi ke nilai Result<T, E>
dan mengembalikan true
jika Result
adalah varian Err
, yang berarti operasi mengalami kesalahan. Jika Result
adalah varian Ok
, metode ini mengembalikan false
.
Metode-metode ini berguna ketika Anda ingin memeriksa keberhasilan atau kegagalan suatu operasi tanpa mengonsumsi nilai Result, memungkinkan Anda melakukan operasi tambahan atau membuat keputusan berdasarkan varian tanpa membungkusnya.
Anda dapat menemukan implementasi dari ResultTrait
di sini.
Selalu lebih mudah dipahami dengan contoh.
Lihatlah tanda tangan fungsi ini:
fn u128_overflowing_add(a: u128, b: u128) -> Result<u128, u128>;
Fungsi ini mengambil dua bilangan bulat u128, a dan b, dan mengembalikan Result<u128, u128>
di mana varian Ok
menyimpan jumlah jika penambahan tidak melampaui batas, dan varian Err
menyimpan nilai yang melampaui batas jika penambahan melampaui batas.
Sekarang, kita bisa 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,
}
}
Di sini, fungsi ini menerima dua bilangan bulat u128, a dan b, dan mengembalikan Option<u128>
. Ini menggunakan Result
yang dikembalikan oleh u128_overflowing_add
untuk menentukan keberhasilan atau kegagalan operasi penambahan. Ekspresi match memeriksa Result
dari u128_overflowing_add
. Jika hasilnya adalah Ok(r)
, itu mengembalikan Option::Some(r)
yang berisi jumlah. Jika hasilnya adalah Err(r)
, itu mengembalikan Option::None
untuk menunjukkan bahwa operasi telah gagal karena melampaui batas. Fungsi ini tidak memicu panic dalam kasus melampaui batas.
Mari kita lihat contoh lain yang menunjukkan penggunaan unwrap
.
{{#include ../listings/ch10-error-handling/listing_01/src/lib.cairo:function}}
Listing 10-1: Menggunakan tipe Result
Dalam contoh ini, fungsi parse_u8
mengambil integer felt252
dan mencoba mengonversinya menjadi integer u8
menggunakan metode try_into
. Jika berhasil, itu mengembalikan Result::Ok(value)
, jika tidak mengembalikan Result::Err('Invalid integer')
.
Dua kasus uji kita adalah:
{{#rustdoc_include ../listings/ch10-error-handling/listing_01/src/lib.cairo:tests}}
Yang pertama menguji konversi yang valid dari felt252
menjadi u8
, mengharapkan metode unwrap
tidak memicu panic. Fungsi uji kedua mencoba mengonversi nilai yang berada di luar rentang u8
, mengharapkan metode unwrap
memicu panic dengan pesan kesalahan 'Invalid integer'.
Kita juga bisa menggunakan atribut #[should_panic] di sini.
?
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 nilai adalah
Result::Ok(x)
atauOption::Some(x)
, itu akan langsung mengembalikan nilai dalamnya, yaitux
. - Jika nilai adalah
Result::Err(e)
atauOption::None
, itu akan menyebarkan kesalahan atauNone
dengan segera keluar dari fungsi.
Operator ?
berguna ketika Anda ingin menangani kesalahan secara implisit dan membiarkan fungsi pemanggil mengatasinya.
Berikut adalah contoh penggunaannya.
{{#include ../listings/ch10-error-handling/listing_02/src/lib.cairo:function}}
Listing 10-1: Menggunakan operator ?
Fungsi do_something_with_parse_u8
mengambil nilai felt252
sebagai input dan memanggil parse_u8
. Operator ?
digunakan untuk menyebarkan kesalahan, jika ada, atau membuka kemasan nilai yang berhasil.
Dan dengan sedikit kasus uji:
{{#rustdoc_include ../listings/ch10-error-handling/listing_02/src/lib.cairo:tests}}
Konsol akan mencetak kesalahan "Invalid Integer".
Ringkasan
Kita melihat bahwa kesalahan yang dapat diperbaiki dapat ditangani di Cairo menggunakan enum Result, yang memiliki dua varian: Ok
dan Err
. Enum Result<T, E>
bersifat generik, dengan tipe T
dan E
mewakili nilai yang berhasil dan nilai kesalahan, secara berturut-turut. ResultTrait
menyediakan metode-metode untuk bekerja dengan Result<T, E>
, seperti membuka nilai, memeriksa apakah hasilnya adalah Ok
atau Err
, dan memicu panic dengan pesan kustom.
Untuk menangani kesalahan yang dapat diperbaiki, 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 kemasan nilai yang berhasil. Ini memungkinkan penanganan kesalahan yang lebih ringkas dan jelas, di mana pemanggil bertanggung jawab untuk mengelola kesalahan yang dibangkitkan oleh fungsi yang dipanggil.