A.39. Tipe Data → Result
Chapter ini membahas tentang tipe data Result
. Tipe data ini digunakan untuk menampung nilai hasil suatu proses yang isinya adalah bisa sukses (Ok
) atau error (Err
).
Tipe data Result
biasa digunakan untuk menampung hasil eksekusi proses dan error handling.
A.39.1. Konsep Result
Tipe data Result
adalah enum dengan isi 2 buah enum value:
Result::Ok<T>
(atauOk<T>
), digunakan untuk menandai bahwa data isinya adalah kabar baik (oke / mantab / jos / sukses).Result::Err<E>
(atauErr<E>
), digunakan untuk menandai bawah data berisi kabar buruk.
T
danE
merupakan parameter generic. Lebih jelasnya mengenai generic dibahas pada chapter Generics.
Tipe data Result
memiliki notasi penulisan Result<T, E>
dimana T
digunakan pada enum value Ok<T>
dan E
digunakan enum value Err<E>
.
Cara penerapan tipe data ini bisa dilihat pada kode berikut:
#[derive(Debug)]
enum MathError {
DivisionByZero,
InfinityNumber,
OtherError,
}
fn main() {
let result1 = divider(10.0, 5.0);
println!("result: {:?}", result1);
let result2: Result<f64, MathError> = divider(10.0, 0.0);
println!("result: {:?}", result2);
}
fn divider(a: f64, b: f64) -> Result<f64, MathError> {
if b == 0.0 {
return Err(MathError::DivisionByZero);
}
let result = a / b;
return Ok(result);
}
Fungsi divider
di atas tugasnya adalah melakukan operasi aritmatika pembagian angka numerik f64
, parameter a
dibagi b
.
Pada fungsi tersebut terdapat pengecekan apabila nilai b
adalah 0
, maka yang dikembalikan adalah Err<E>
dengan E
berisi pesan error, selainnya maka hasil operasi pembagian dikembalikan dibungkus dalam enum value Ok<f64>
.
Fungsi divider
nilai baliknya bertipe Result<f64, MathError>
. Dari tipe data yang digunakan nantinya bisa diprediksi pasti akan ada 2 potensi value:
- Return value adalah enum value
Err<MathError>
, muncul ketika nilaib
adalah0
- Return value adalah nilai hasil numerik yang dibungkus oleh enum value
Ok<f64>
Output program di atas saat di-run:
A.39.2. Pattern matching pada tipe Result
Dalam penerapannya, ketika ada data bertipe Result
artinya data tersebut berpotensi untuk berisi nilai Err<E>
atau Ok<T>
, pasti antara 2 nilai tersebut.
Umumnya penggunaan tipe Result
selalu diikuti dengan pattern matching menggunakan keyword match
. Selain itu keyword if
sebenarnya juga bisa diterapkan pada pattern matching tipe data ini, namun kurang dianjurkan.
Mari kita praktikkan. Ubah isi fungsi main
dengan kode berikut:
let result = divider(10.0, 5.0);
match result {
Err(m) => println!("ERROR! {:?}", m),
Ok(r) => println!("result: {r:.2}"),
}
Bisa dilihat pada kode di atas mudahnya pengambilan nilai m
dari Err(m)
dan juga r
dari Ok(r)
. Penerapan match
untuk seleksi kondisi biasa disebut dengan pattern matching dan teknik ini sangat fleksibel dan advance.
Sebagai contoh, dengan penerapan match yang seperti ini kita bisa meng-handle 5 skenario seleksi kondisi:
let result = divider(10.0, 5.0);
match result {
Err(MathError::DivisionByZero) => println!("ERROR! unable to divide number by 0"),
Err(MathError::InfinityNumber) => println!("ERROR! result is infinity number (∞)"),
Err(_) => println!("ERROR! unknown error"),
Ok(2.0) => println!("the result is 2"),
Ok(x) => println!("result: {x:.2}"),
}
- Kondisi ke-1: jika nilai adalah
Err(MathError::DivisionByZero)
, maka munculkan pesanERROR! unable to divide number by 0
. - Kondisi ke-2: jika nilai adalah
Err(MathError::InfinityNumber)
, maka munculkan pesanERROR! result is infinity number (∞)
. - Kondisi ke-3: jika nilai adalah
Err
selain dariErr(MathError::DivisionByZero)
danErr(MathError::InfinityNumber)
, maka munculkan pesanERROR! unknown error
. - Kondisi ke-4: jika nilai adalah
Ok(2.0)
, maka munculkan pesanthe result is 2
. - Kondisi ke-5: jika nilai adalah
Ok
selain dariOk(2.0)
, maka munculkan pesanresult: {x:.2}
.
◉ Tips pattern matching
Silakan perhatikan kode yang sudah kita praktikkan berikut ini:
let result = divider(10.0, 5.0);
match result {
Err(m) => println!("ERROR! {:?}", m),
Ok(r) => println!("result: {r:.2}"),
}
Penerapan pattern matching seperti contoh di atas memiliki konsekuensi, yaitu variabel r
hanya bisa diakses pada block Ok(r)
saja.
Adakalanya kita butuh untuk mengeluarkan variabel r
ke luar block. Hal seperti ini mudah untuk dilakukan, dan ada beberapa cara yang bisa dipilih, namun menurut penulis yang paling elegan adalah cara berikut ini:
fn main() {
let result: f64 = match divider(10.0, 5.0) {
Err(m) => {
println!("ERROR! {:?}", m);
0.0
},
Ok(r) => r,
};
println!("result: {:?}", result);
}
Statement divider(10.0, 5.0)
mengembalikan data bertipe Result<f64, MathError>
. Data tersebut digunakan pada keyword match
seperti biasa. Namun pada contoh di atas ada yang berbeda, yaitu return value dari statement match
ditampung ke variabel (result
).
Isi dari pattern matching match
sendiri ada dua:
- Ketika block
Err(m)
match, error di-print kemudian nilai0.0
dijadikan return statementmatch
. - Ketika block
Ok
match, datar
dijadikan return value statementmatch
.
Dengan penerapan pattern matching seperti di atas, maka variabel result
akan selalu berisi data hasil operasi divider(10.0, 5.0)
. Dengan pengecualian ketika ada error, pesan errornya dimunculkan kemudian hasil operasi pembagian di-set sebagai 0.0
.
Lebih jelasnya mengenai pattern matching dibahas pada chapter Pattern Matching
A.39.3. Method tipe data Result
◉ Method is_ok
& unwrap
Isi dari enum value Ok<T>
bisa diakses tanpa menggunakan keyword match
dengan cara memanfaatkan method unwrap
milik Result<T, E>
. Sebelum mengakses method tersebut sangat dianjurkan untuk mengecek apakah data berisi Ok<T> atau tidak
, karena jika data adalah Err<E>
pengaksesan method unwrap
menghasilkan error.
Pengecekan nilai ok atau tidak bisa dilakukan menggunakan method is_ok
.
let result = divider(10.0, 5.0);
if result.is_ok() {
let number = result.unwrap();
println!("result: {}", number);
// result: 2
}
◉ Method as_ref
Method as_ref
digunakan untuk mengakses reference T
dan E
pada Result<T, E>
. Method ini sering kali dibutuhkan untuk menghindari terjadinya move semantics pada owner data bertipe Result<T, E>
.
Method as_ref
mengembalikan data dalam tipe Result<&T, &E>
. Jadi reference yang dipinjam bukan milik Result
-nya melainkan milik T
dan E
.
let result: Result<f64, MathError> = divider(10.0, 0.0);
let result_borrow: Result<&f64, &MathError> = result.as_ref();
Lebih jelasnya mengenai move semantics dibahas pada chapter Ownership
◉ Method is_err
& err
Method err
mengembalikan data dalam tipe Err<E>
. Pada pengaksesan method ini, pastikan untuk mengecek apakah Result
berisi data error atau ok dengan via method is_err
. Selain itu, wajib untuk menggunakan method as_ref
sebelum method err
agar ownership data Result
tidak berpindah (move semantics).
let result = divider(10.0, 0.0);
if result.is_err() {
let err = result.as_ref().err();
let message = err.unwrap();
println!("error: {:?}", message);
// error: DivisionByZero
}
◉ Method ok
Aturan yang sama juga berlaku pada pengaksesan method ok
yang mengembalikan data Ok<T>
. Method as_ref
harus diakses terlebih dahulu sebelum memanggil method ok
agar tidak terjadi move semantics.
let result = divider(10.0, 5.0);
if result.is_ok() {
let data = result.as_ref().ok();
let number = data.unwrap();
println!("result: {:?}", number);
// result: 2
}
◉ Method unwrap_or_default
Method unwrap_or_default
milik Result<T, E>
mengembalikan nilai T
ketika data berisi Ok<T>
, namun jika data berisi Err<E>
maka yang dikembalikan adalah default value dari tipe data T
.
let result = divider(10.0, 0.0);
let number = result.unwrap_or_default();
println!("result: {}", number);
// result: 0
◉ Method unwrap_or
Method unwrap_or
milik Result<T, E>
mengembalikan nilai T
ketika data berisi Ok<T>
, namun jika data ternyata isinya adalah Err<E>
maka yang dikembalikan adalah argument pemanggilan method tersebut.
let result = divider(10.0, 0.0);
let number = result.unwrap_or(0.0);
println!("result: {}", number);
// result: 0
◉ Method unwrap_or_else
Method ini mengembalikan nilai T
ketika data berisi Ok<T>
, namun jika data isinya adalah Err<E>
maka yang dikembalikan adalah hasil eksekusi closure yang disisipkan saat memanggil method unwrap_or_else
. Contoh pengaplikasiannya:
let result = divider(10.0, 0.0);
let number = result.unwrap_or_else(|_| 0.0);
println!("result: {}", number);
// result: 0
Closure harus dalam notasi FnOnce(E) -> T
dimana T
pada konteks ini adalah f64
.
Lebih jelasnya mengenai closure dibahas pada chapter Closures.
A.39.4. Error handling tipe Result
Tipe data Result<T, E>
banyak digunakan pada fungsi milik Rust standard library, dan kita selaku programmer pastinya juga akan menggunakannya dalam real life project.
Tipe ini dipakai salah satunya untuk manajemen error. Lebih jelasnya mengenai topik tersebut dibahas pada chapter Error Handling & Panic
Catatan chapter 📑
◉ Source code praktik
github.com/novalagung/dasarpemrogramanrust-example/../result_type
◉ Chapter relevan lainnya
◉ Work in progress
- Operator
?