Keluarga unwrap_or mengambil isi Result secepat mungkin; namun sering kali kita justru perlu mengolah isinya lebih dulu. Parser usia yang berhasil menghasilkan Ok(34) mungkin harus dikonversi ke tahun kelahiran; error mentah ParseIntError yang dibaca pemanggil mungkin perlu dibungkus ulang menjadi pesan konfigurasi yang manusiawi. Kedua kebutuhan itu bisa ditulis dengan match, tetapi pola tersebut berulang sampai membosankan: buka bungkus, ubah isinya, bungkus lagi. Rust menyediakan dua method yang mengerjakan ritual itu — map untuk jalur sukses dan map_err untuk jalur kegagalan. Keduanya bekerja seperti ban berjalan: nilai Ok masuk dari satu ujung, keluar di ujung lain dalam bentuk baru, sementara jalur Err dilewatkan atau ditransformasikan tanpa pernah dibuka paksa. Pada chapter ini kita akan melihat keduanya bekerja, lalu menggabungkannya dengan unwrap_or.
map: Mengubah Isi Ok #
Method map(f) menerima closure yang hanya dijalankan ketika Result berisi Ok: isi Ok dimasukkan ke closure, hasilnya dibungkus ulang menjadi Ok baru. Ketika Result berisi Err, closure dilewati sama sekali dan error mengalir keluar tanpa disentuh. Program berikut menggandakan hasil parsing:
fn main() {
let ganda_valid = "50".parse::<u32>().map(|nilai| nilai * 2);
let ganda_rusak = "lima puluh".parse::<u32>().map(|nilai| nilai * 2);
println!("'50' : {:?}", ganda_valid);
println!("'lima puluh': {:?}", ganda_rusak);
}Output yang Dihasilkan:
'50' : Ok(100)
'lima puluh': Err(ParseIntError { kind: InvalidDigit })Mari kita bedah kode di atas:
- Baris 2 (
"50".parse::<u32>().map(|nilai| nilai * 2)): parsing menghasilkanOk(50); closure|nilai| nilai * 2menerima50, mengembalikan100, danmapmembungkusnya kembali menjadiOk(100). Tipe hasil akhirnyaResult<u32, ParseIntError>— bungkus tetap terpasang, hanya isinya yang berubah. - Baris 3 (
"lima puluh".parse::<u32>().map(...)): parsing menghasilkanErr(ParseIntError); closure tidak pernah dieksekusi, dan error yang sama mengalir keluarmaptanpa diubah. Perhatikan outputnya: isiErrmasihParseIntErroryang utuh. - Kedua variabel bertipe sama:
Result<u32, ParseIntError>pada kedua jalur — inilah yang membuatmapkomposabel; hasilnya siap diproses methodResultlain.
Kunci perilakunya: map mengubah tipe parameter sukses T (dari u32 berisi 50 menjadi u32 berisi 100 — atau bisa saja tipe benar-benar berbeda, misalnya u32 menjadi String), tetapi tipe kegagalan E dibiarkan apa adanya. Fungsi map tidak pernah tahu kegagalan itu seperti apa; fungsi hanya memastikan jalur sukses dimodifikasi.
map_err: Mengubah Isi Err #
Method map_err(f) adalah cermin dari map: closure hanya dijalankan ketika Result berisi Err, sedangkan Ok dibiarkan mengalir. Kegunaan utamanya adalah menerjemahkan error mentah menjadi bentuk yang lebih bermakna bagi pemanggil. Program berikut membaca port server dan mengganti ParseIntError dengan String berisi pesan konfigurasi:
fn parse_port(input: &str) -> Result<u16, String> {
input
.parse::<u16>()
.map_err(|error| format!("'{}' bukan port yang valid: {}", input, error))
}
fn main() {
match parse_port("8080") {
Ok(port) => println!("Server berjalan di port {}", port),
Err(pesan) => println!("Konfigurasi ditolak: {}", pesan),
}
match parse_port("abc") {
Ok(port) => println!("Server berjalan di port {}", port),
Err(pesan) => println!("Konfigurasi ditolak: {}", pesan),
}
}Output yang Dihasilkan:
Server berjalan di port 8080
Konfigurasi ditolak: 'abc' bukan port yang valid: invalid digit found in stringMari kita telaah kode di atas:
- Baris 1 (
fn parse_port(input: &str) -> Result<u16, String>): tipe kegagalan di tanda tangan adalahString, bukanParseIntError— fungsi berjanji mengirim pesan yang sudah diterjemahkan ke pemanggil. - Baris 4 (
.map_err(|error| format!(...))): closure menerimaParseIntErrordan memakai macroformat!— sepupuprintln!yang tidak mencetak ke layar, melainkan menghasilkanString— untuk membangun pesan lengkap berisi teks masukan dan isi error aslinya. Teks"8080"sukses, jadi closure tidak berjalan danOk(8080)lolos tanpa disentuh. - Pemanggilan kedua (
parse_port("abc")): parsing gagal, closure berjalan dan menghasilkan pesan'abc' bukan port yang valid: invalid digit found in string; pemanggil menerimaErr(String)dan cabangErrcukup mencetak pesan tanpa perlu tahu strukturParseIntError.
Pola “terjemahkan error di batas fungsi” ini penting untuk desain yang bersih: detail teknis parsing disembunyikan di dalam parse_port, sementara pemanggil hanya berhadapan dengan pesan yang siap ditampilkan.
map Menggantikan match Boilerplate #
Untuk melihat seberapa banyak ritual yang dihemat, bandingkan dua fungsi identik — satu memakai match, satu memakai map:
fn gandakan_verbose(input: &str) -> Result<u32, std::num::ParseIntError> {
let hasil = input.parse::<u32>();
match hasil {
Ok(nilai) => Ok(nilai * 2),
Err(error) => Err(error),
}
}
fn gandakan(input: &str) -> Result<u32, std::num::ParseIntError> {
input.parse::<u32>().map(|nilai| nilai * 2)
}
fn main() {
println!("Verbose: {:?}", gandakan_verbose("50"));
println!("Compact: {:?}", gandakan("50"));
println!("Compact: {:?}", gandakan("abc"));
}Output yang Dihasilkan:
Verbose: Ok(100)
Compact: Ok(100)
Compact: Err(ParseIntError { kind: InvalidDigit })Bedah perbandingannya:
- Baris 1-7 (
fn gandakan_verbose): versimatchharus membuka bungkus, menghitungnilai * 2di cabangOk, membungkus ulang denganOk(...), lalu menulis cabangErryang hanya memindahkan error ke tempat yang sama — lima baris yang sebagian besar adalah upacara. - Baris 9-10 (
fn gandakan): versimapmenyatakan niatnya dalam satu baris: “parse, lalu kalikan dua”. CabangErrtidak pernah hilang —mapyang menanganinya secara implisit — tetapi kode kita berhenti mengulanginya secara manual. - Baris 14-16 (pemanggilan): hasil kedua fungsi identik untuk masukan yang sama, membuktikan
mapbenar-benar setara denganmatchversi verbose.
Alur nilai lewat kedua method tersebut dapat kita petakan:
Diagram di atas menegaskan pembagian tugas: map menyentuh jalur Ok, map_err menyentuh jalur Err, dan keduanya tidak pernah memaksa kita membuka bungkus di kode aplikasi.
Ringkasan #
map mengubah isi Ok lewat closure tanpa menyentuh Err, sedangkan map_err mengubah isi Err tanpa menyentuh Ok — keduanya mengembalikan Result sehingga hasilnya bisa dirangkai. map_err menjadi alat standar menerjemahkan error mentah seperti ParseIntError menjadi pesan aplikasi di batas fungsi, dan map menghapus boilerplate match yang membuka-bungkus nilai hanya untuk menghitungnya ulang. Hingga titik ini seluruh penanganan kita berhenti di satu fungsi; kegagalan yang terjadi jauh di dalam rantai pemanggilan masih harus diteruskan keluar secara manual. Chapter berikutnya memperkenalkan operator tanya ? yang memangkas propagasi tersebut menjadi satu karakter.