Trik Pustaka Standar di Rust: Idiom Kecil untuk Sehari-hari
Setiap dapur punya laci berisi alat-alat kecil: pengupas, pembuka botol, penghancur bawang putih. Sebenarnya semua pekerjaan bisa kamu lakukan dengan satu pisau besar, tapi alat-alat kecil itu membuat pekerjaan lebih cepat dan lebih aman. Pustaka standar (standard library) Rust juga punya laci seperti itu.
Halaman ini mengajakmu berkeliling isi laci itu. Setiap trik dibuat singkat: gambaran dari kehidupan sehari-hari, contoh yang bisa dijalankan, dan satu baris “pakai ini kalau…” supaya kamu tahu kapan memakainya. Kamu tidak perlu menghafal semuanya sekaligus. Baca sekilas dulu, lalu kembali lagi ke sini saat kamu melihat pola di kodemu sendiri yang terasa kaku.
Memindahkan nilai keluar dari &mut: mem::take, replace, swap
Section titled “Memindahkan nilai keluar dari &mut: mem::take, replace, swap”Bayangkan tempat sampah yang ada kantongnya. Kamu boleh mengambil kantong yang sudah penuh, asalkan langsung memasang kantong baru. Tempat sampah itu tidak pernah dibiarkan tanpa kantong.
Rust punya aturan yang sama. Kalau kamu hanya punya &mut self, kamu tidak bisa begitu saja memindahkan sebuah field keluar, karena itu akan meninggalkan “lubang” di dalam struct. Compiler akan menghentikanmu dengan E0507. Fungsi-fungsi di std::mem menyelesaikan masalah ini dengan selalu meninggalkan sesuatu sebagai pengganti:
mem::take(&mut x)mengambil nilainya dan meninggalkan nilai default tipe itu (Veckosong,Stringkosong,0,None).mem::replace(&mut x, new)memasukkannewdan memberimu nilai yang lama.Option::take()mengambil isi sebuahOptiondan meninggalkanNone.
use std::mem;
struct Inbox { unread: Vec<String>, status: String, draft: Option<String>,}
impl Inbox { // Serahkan semua pesan yang belum dibaca dan tinggalkan Vec kosong. fn read_all(&mut self) -> Vec<String> { mem::take(&mut self.unread) }
// Pasang status baru dan dapatkan status yang lama. fn set_status(&mut self, new: &str) -> String { mem::replace(&mut self.status, new.to_string()) }
// Ambil draf (kalau ada) dan tinggalkan None. fn send_draft(&mut self) -> Option<String> { self.draft.take() }}
fn main() { let mut inbox = Inbox { unread: vec!["Makan siang?".to_string(), "Tagihan #42".to_string()], status: "online".to_string(), draft: Some("Sampai ketemu jam 6".to_string()), };
let messages = inbox.read_all(); println!("dibaca {:?}, belum dibaca: {:?}", messages, inbox.unread);
let old = inbox.set_status("sedang rapat"); println!("status tadi '{old}', sekarang '{}'", inbox.status);
println!("terkirim: {:?}", inbox.send_draft()); println!("terkirim lagi: {:?}", inbox.send_draft());}Output:
dibaca ["Makan siang?", "Tagihan #42"], belum dibaca: []status tadi 'online', sekarang 'sedang rapat'terkirim: Some("Sampai ketemu jam 6")terkirim lagi: NoneTidak ada clone() sama sekali: pesan-pesan dan status lama itu dipindahkan keluar, bukan disalin. Itulah sebabnya fungsi-fungsi ini aman sekaligus murah.
Dua “kerabat” melengkapi set ini. mem::swap(&mut a, &mut b) menukar dua nilai, seperti menukar dua piring di atas meja. Option::replace(new) memasukkan nilai baru ke dalam Option dan mengembalikan nilai yang lama.
use std::mem;
fn main() { // Isi satu daftar selagi daftar yang lain sedang dikerjakan. let mut filling = vec!["pesanan 1", "pesanan 2"]; let mut processing: Vec<&str> = Vec::new();
mem::swap(&mut filling, &mut processing); println!("diproses {processing:?}, diisi {filling:?}");
// Option::replace: masukkan nilai baru, dapatkan nilai lama. let mut now_playing = Some("Lagu A"); let previous = now_playing.replace("Lagu B"); println!("tadi {previous:?}, sekarang {now_playing:?}");}Output:
diproses ["pesanan 1", "pesanan 2"], diisi []tadi Some("Lagu A"), sekarang Some("Lagu B")Pakai ini kalau kamu hanya punya akses &mut, tapi perlu memiliki nilai di dalamnya.
Slice: windows, chunks dan kawan-kawannya
Section titled “Slice: windows, chunks dan kawan-kawannya”Anggap windows(2) sebagai kaca pembesar yang digeser di sepanjang penggaris: ia menunjukkan dua garis sekaligus, lalu bergeser satu langkah. chunks(3) seperti mengemas telur ke kotak isi tiga: kotak terakhir mungkin tidak penuh. chunks_exact(3) hanya memberimu kotak yang penuh dan menyimpan sisa telurnya di .remainder().
Method-method ini bekerja pada slice (potongan data) apa saja, jadi juga bisa dipakai pada array dan vektor.
fn main() { let temps = [12, 15, 14, 19, 21];
// windows(2): setiap pasangan tetangga, saling tumpang tindih for pair in temps.windows(2) { println!("{} -> {}: berubah {}", pair[0], pair[1], pair[1] - pair[0]); }
// chunks(3): kelompok isi 3, yang terakhir bisa lebih kecil let emails = ["[email protected]", "[email protected]", "[email protected]", "[email protected]", "[email protected]"]; for batch in emails.chunks(3) { println!("mengirim ke {batch:?}"); }
// chunks_exact(2): hanya kelompok penuh; sisanya menunggu di remainder() let seats = [1, 2, 3, 4, 5]; let pairs = seats.chunks_exact(2); let leftover = pairs.remainder(); for pair in pairs { println!("pasangan {pair:?}"); } println!("sisa: {leftover:?}");}Output:
12 -> 15: berubah 315 -> 14: berubah -114 -> 19: berubah 519 -> 21: berubah 2mengirim ke ["[email protected]", "[email protected]", "[email protected]"]mengirim ke ["[email protected]", "[email protected]"]pasangan [1, 2]pasangan [3, 4]sisa: [5]Beberapa helper slice lain bisa menjawab pertanyaan umum tanpa perulangan. split_first() memberimu item pertama dan sisanya, seperti guru yang membacakan nama pertama di daftar lalu menyerahkan sisa daftarnya ke asisten. split_last() melakukan hal yang sama dari ujung belakang.
fn main() { let command = ["git", "commit", "-m", "perbaiki typo"];
// split_first: item pertama, dan slice berisi sisanya if let Some((program, args)) = command.split_first() { println!("program: {program}, argumen: {args:?}"); }
// split_last: item terakhir, dan semua yang ada sebelumnya if let Some((message, _rest)) = command.split_last() { println!("pesan: {message}"); }
println!("apakah git? {}", command.starts_with(&["git"])); println!("ada -m? {}", command.contains(&"-m"));
// iter().rev(): berjalan mundur tanpa mengubah slice-nya let history = ["buka", "edit", "simpan"]; let undo_order: Vec<_> = history.iter().rev().collect(); println!("urutan undo: {undo_order:?}");}Output:
program: git, argumen: ["commit", "-m", "perbaiki typo"]pesan: perbaiki typoapakah git? trueada -m? trueurutan undo: ["simpan", "edit", "buka"]split_first dan split_last mengembalikan Option, karena slice kosong tidak punya item pertama. Itulah sebabnya kita memakai if let.
Pakai ini kalau kamu butuh tetangga (windows), kelompok (chunks), atau “yang pertama dan sisanya” (split_first).
Helper iterator yang akan sering kamu pakai
Section titled “Helper iterator yang akan sering kamu pakai”Halaman Iterator sudah membahas map, filter, sum dan collect. Berikut alat-alat berikutnya di kotak perkakas.
peekable: intip dulu sebelum mengambil
Section titled “peekable: intip dulu sebelum mengambil”Bayangkan kamu sedang membagikan kartu. Kadang kamu ingin melihat kartu berikutnya sebelum memutuskan untuk mengambilnya. .peekable() memberimu iterator yang bisa melakukan itu: .peek() menunjukkan item berikutnya tanpa mengambilnya, dan .next_if(...) hanya mengambilnya kalau item itu lolos tes. Ini persis yang dibutuhkan parser kecil.
fn main() { let input = "12+345-6"; let mut chars = input.chars().peekable(); let mut tokens: Vec<String> = Vec::new();
while let Some(c) = chars.next() { if c.is_ascii_digit() { let mut number = c.to_string(); // Ambil karakter berikutnya hanya selama itu juga angka. while let Some(d) = chars.next_if(|next| next.is_ascii_digit()) { number.push(d); } tokens.push(number); } else { tokens.push(c.to_string()); } }
println!("{tokens:?}");}Output:
["12", "+", "345", "-", "6"]Tanpa peekable, kamu akan ikut membaca + saat mencari angka berikutnya, dan setelah itu + tersebut sudah hilang.
Pakai ini kalau item berikutnya menentukan apa yang kamu lakukan dengan item sekarang.
Menelusuri daftar: zip, enumerate, step_by, take_while, skip_while, chain
Section titled “Menelusuri daftar: zip, enumerate, step_by, take_while, skip_while, chain”| Method | Bayangkan seperti | Pakai ini kalau… |
|---|---|---|
zip |
ritsleting yang menyatukan dua sisi gigi demi gigi | kamu punya dua daftar yang saling berpasangan |
enumerate |
memberi nomor pada baris-baris daftar | kamu butuh indeks sekaligus item-nya |
step_by(n) |
naik tangga dua (atau n) anak tangga sekaligus | kamu ingin setiap item ke-n |
take_while |
makan permen sampai ketemu yang tidak kamu suka | kamu ingin awal daftar, sampai “tidak” yang pertama |
skip_while |
melewati iklan sampai acaranya mulai | kamu ingin sisanya, mulai dari “tidak” yang pertama |
chain |
menyambung dua gerbong kereta | kamu ingin satu daftar disusul daftar lain |
fn main() { let names = ["Ana", "Ben", "Chen"]; let scores = [88, 92, 75];
// zip memasangkan dua daftar; enumerate menambah penghitung mulai dari 0 for (i, (name, score)) in names.iter().zip(scores.iter()).enumerate() { println!("{}. {name}: {score}", i + 1); }
// step_by: setiap menit ke-15 dalam satu jam let quarters: Vec<u32> = (0..60).step_by(15).collect(); println!("tiap seperempat jam: {quarters:?}");
// into_iter() pada array memberi angka-angkanya langsung let queue = [3, 5, 8, 12, 4]; let small: Vec<i32> = queue.into_iter().take_while(|n| *n < 10).collect(); let rest: Vec<i32> = queue.into_iter().skip_while(|n| *n < 10).collect(); println!("sebelum angka besar pertama: {small:?}, mulai dari situ: {rest:?}");
// chain: satu daftar disusul daftar lainnya let morning = ["email", "standup"]; let afternoon = ["review", "deploy"]; let day: Vec<_> = morning.iter().chain(afternoon.iter()).collect(); println!("{day:?}");}Output:
1. Ana: 882. Ben: 923. Chen: 75tiap seperempat jam: [0, 15, 30, 45]sebelum angka besar pertama: [3, 5, 8], mulai dari situ: [12, 4]["email", "standup", "review", "deploy"]Perhatikan bahwa take_while berhenti di 12 untuk selamanya. Angka 4 sesudahnya memang kecil, tapi tidak pernah diperiksa. Itulah bedanya dengan filter, yang memeriksa setiap item.
Menjawab pertanyaan: fold, position, find_map, partition, flat_map
Section titled “Menjawab pertanyaan: fold, position, find_map, partition, flat_map”| Method | Bayangkan seperti | Pakai ini kalau… |
|---|---|---|
sum |
tombol tambah di kalkulator | kamu cuma ingin totalnya |
fold |
bola salju yang menggelinding turun, makin besar di setiap langkah | kamu membangun satu hasil dengan caramu sendiri |
position |
“Ben duduk di kursi nomor berapa?” | kamu butuh indeks dari kecocokan pertama |
find_map |
mencoba kunci satu per satu sampai pintunya terbuka | kamu mencoba sesuatu pada tiap item dan menyimpan keberhasilan pertama |
partition |
memilah cucian ke dua keranjang | kamu membagi satu daftar jadi dua berdasarkan pertanyaan ya/tidak |
flat_map |
menuang isi beberapa tas ke satu meja | setiap item berubah menjadi beberapa item |
fn main() { let orders = [12, 40, 7, 55, 30];
// sum: cukup jumlahkan semuanya let total: i32 = orders.iter().sum();
// fold: jumlahkan dengan caramu sendiri (di sini: tiap pesanan kena biaya admin 2) let with_fees = orders.iter().fold(0, |acc, price| acc + price + 2); println!("total {total}, dengan biaya {with_fees}");
// position: indeks dari kecocokan pertama println!("pesanan pertama di atas 50 ada di indeks {:?}", orders.iter().position(|p| *p > 50));
// partition: dua Vec, satu untuk "ya" dan satu untuk "tidak" let (big, small): (Vec<i32>, Vec<i32>) = orders.into_iter().partition(|p| *p >= 30); println!("besar {big:?}, kecil {small:?}");
// find_map: coba parse satu per satu, simpan yang pertama berhasil let inputs = ["abc", "", "42", "7"]; let first_number = inputs.iter().find_map(|s| s.parse::<i32>().ok()); println!("angka pertama: {first_number:?}");
// flat_map: setiap baris menjadi beberapa kata, semuanya dalam satu daftar let lines = ["beli susu", "telepon ibu sekarang"]; let words: Vec<&str> = lines.iter().flat_map(|line| line.split(' ')).collect(); println!("{words:?}");}Output:
total 144, dengan biaya 154pesanan pertama di atas 50 ada di indeks Some(3)besar [40, 55, 30], kecil [12, 7]angka pertama: Some(42)["beli", "susu", "telepon", "ibu", "sekarang"]fold menerima nilai awal (0) dan sebuah closure (fungsi anonim) yang mendapat “hasil sejauh ini” dan item berikutnya. Kalau kamu cuma butuh total biasa, sum menyampaikannya dengan lebih jelas. Pakai fold saat aturannya kamu buat sendiri.
Merapikan Vec di tempat: dedup dan retain
Section titled “Merapikan Vec di tempat: dedup dan retain”Dua ini adalah method Vec, bukan method iterator, tapi tempatnya tetap di laci yang sama. dedup itu seperti DJ yang tidak pernah memutar lagu yang sama dua kali berturut-turut (lagunya boleh muncul lagi nanti). retain itu penjaga pintu: hanya item yang lolos tes yang boleh tetap di dalam.
fn main() { // dedup menghapus pengulangan yang letaknya bersebelahan let mut clicks = vec!["beranda", "beranda", "toko", "toko", "toko", "keranjang", "beranda"]; clicks.dedup(); println!("{clicks:?}");
// retain hanya menyimpan item yang lolos tes let mut cart = vec![("apel", 3), ("roti", 0), ("susu", 1), ("telur", 0)]; cart.retain(|(_, qty)| *qty > 0); println!("{cart:?}");}Output:
["beranda", "toko", "keranjang", "beranda"][("apel", 3), ("susu", 1)]"beranda" yang terakhir tetap ada karena letaknya tidak bersebelahan dengan "beranda" lain. Untuk menghapus semua duplikat, urutkan dulu datanya, seperti yang ditunjukkan di halaman Pengurutan.
Pakai ini kalau kamu ingin mengubah sebuah Vec tanpa membuat yang baru.
Kemudahan untuk Option dan Result
Section titled “Kemudahan untuk Option dan Result”Option itu seperti paket yang mungkin sudah datang, mungkin juga belum. Kamu bisa membuka setiap paket dengan match lengkap, tapi biasanya satu helper pendek sudah cukup untuk menyampaikan hal yang sama. Halaman Option membahas unwrap_or dan match; berikut helper-helper berikutnya.
fn main() { let nickname: Option<String> = None; let age: Option<u32> = Some(17); let width: Option<u32> = Some(3); let height: Option<u32> = Some(4);
// unwrap_or_default: nilainya, atau default tipe itu ("", 0, Vec kosong...) println!("nama panggilan: '{}'", nickname.unwrap_or_default());
// map_or: nilai cadangan kalau None, kalau tidak, ubah nilainya println!("{}", age.map_or("umur tidak diketahui".to_string(), |a| format!("{a} tahun")));
// filter: simpan nilainya hanya kalau lolos tes println!("umur dewasa: {:?}", age.filter(|a| *a >= 18));
// zip: Some hanya kalau KEDUANYA Some println!("luas: {:?}", width.zip(height).map(|(w, h)| w * h));
// and_then: sambung dengan langkah yang juga bisa menghasilkan None let good = Some("42").and_then(|s| s.parse::<u32>().ok()); let bad = Some("empat puluh").and_then(|s| s.parse::<u32>().ok()); println!("hasil parse: {good:?} dan {bad:?}");}Output:
nama panggilan: ''17 tahunumur dewasa: Noneluas: Some(12)hasil parse: Some(42) dan NoneDi dalam fungsi, ? juga bisa dipakai pada Option: kalau nilainya None, fungsi langsung mengembalikan None. Di fungsi yang mengembalikan Result, ubah dulu Option-nya menjadi Result dengan ok_or(error) atau ok_or_else(|| error). Pakai ok_or_else kalau membuat error-nya butuh usaha, karena closure itu hanya dijalankan saat diperlukan.
struct Employee { name: String, manager: Option<String>,}
// `?` pada Option: kalau None, langsung kembalikan None.fn manager_initial(e: &Employee) -> Option<char> { let manager = e.manager.as_ref()?; manager.chars().next()}
// ok_or_else mengubah None menjadi error, jadi `?` bisa dipakai di fungsi Result.fn port_from(setting: Option<&str>) -> Result<u16, String> { let raw = setting.ok_or_else(|| "PORT belum diatur".to_string())?; raw.parse::<u16>().map_err(|e| e.to_string())}
fn main() { let ana = Employee { name: "Ana".to_string(), manager: Some("Grace".to_string()) }; let grace = Employee { name: "Grace".to_string(), manager: None }; println!("{}: {:?}", ana.name, manager_initial(&ana)); println!("{}: {:?}", grace.name, manager_initial(&grace));
println!("{:?}", port_from(Some("8080"))); println!("{:?}", port_from(None)); println!("{:?}", port_from(Some("delapan puluh")));
// transpose: Option<Result<..>> menjadi Result<Option<..>> let retries_setting: Option<&str> = Some("3"); let retries: Result<Option<u8>, _> = retries_setting.map(|s| s.parse::<u8>()).transpose(); println!("{retries:?}");}Output:
Ana: Some('G')Grace: NoneOk(8080)Err("PORT belum diatur")Err("invalid digit found in string")Ok(Some(3))Pesan invalid digit found in string berasal dari Rust sendiri (pesan error bawaan saat parse gagal), jadi tetap dalam bahasa Inggris. transpose memang jarang dipakai, tapi ia jawaban yang rapi untuk situasi “aku punya pengaturan opsional, dan parse-nya bisa gagal”. Dengannya, kamu bisa memasang ? pada hasilnya dan tetap menyimpan Option-nya.
Pakai ini kalau sebuah match pada Option cuma punya satu cabang yang menarik dan satu cabang yang membosankan.
Entry API pada HashMap
Section titled “Entry API pada HashMap”Bayangkan meja penitipan jaket. Kamu menyebutkan sebuah nama, lalu petugas mencari gantungan itu atau memasang yang baru, kemudian menyerahkannya padamu. Cukup satu kali jalan, bukan dua. Itulah .entry(key). Halaman HashMap menunjukkan entry(key).or_insert(value), si penghitung klasik. Ada dua variasi lagi yang perlu kamu tahu:
or_insert_with(f)hanya membuat nilai default saat key-nya belum ada.or_default()melakukan hal yang sama dengan nilai default tipe itu.and_modify(f)mengubah nilainya kalau key-nya sudah ada; sambung denganor_insertsesudahnya untuk kasus “key baru”.
use std::collections::HashMap;
fn main() { let sales = [("apel", 3), ("pir", 2), ("apel", 5), ("plum", 1), ("pir", 4)];
// and_modify + or_insert: perbarui kalau ada, sisipkan kalau belum let mut totals: HashMap<&str, i32> = HashMap::new(); for (fruit, qty) in sales { totals.entry(fruit).and_modify(|t| *t += qty).or_insert(qty); }
// or_insert_with: buat Vec kosong hanya saat dibutuhkan let mut orders: HashMap<&str, Vec<i32>> = HashMap::new(); for (fruit, qty) in sales { orders.entry(fruit).or_insert_with(Vec::new).push(qty); }
// Urutan HashMap acak, jadi urutkan key-nya sebelum dicetak let mut fruits: Vec<&str> = totals.keys().copied().collect(); fruits.sort(); for fruit in fruits { println!("{fruit}: total {}, pesanan {:?}", totals[fruit], orders[fruit]); }}Output:
apel: total 8, pesanan [3, 5]pir: total 6, pesanan [2, 4]plum: total 1, pesanan [1]Pakai ini kalau tanpa itu kamu akan menulis “kalau key-nya ada, ubah; kalau tidak, sisipkan”, yang mencari key yang sama dua kali.
Never type !: kode yang tidak pernah kembali
Section titled “Never type !: kode yang tidak pernah kembali”Ada jalan yang satu arah: sekali masuk, kamu tidak pernah keluar lagi. Di Rust, ekspresi yang tidak pernah selesai punya never type (tipe “tidak pernah”), ditulis !. panic!, return, break, continue, loop tanpa akhir, dan std::process::exit semuanya bertipe ini.
Karena nilai bertipe ! tidak pernah benar-benar ada, compiler mengizinkannya menggantikan tipe apa pun. Itulah sebabnya sebuah match boleh punya satu cabang yang menghasilkan i32 dan cabang lain yang melakukan return: cabang kedua tidak pernah menghasilkan nilai, jadi tidak bisa merusak kecocokan tipenya.
use std::process;
// `-> !` artinya "fungsi ini tidak pernah kembali"fn quit(message: &str) -> ! { println!("{message}"); process::exit(0)}
fn level_points(level: &str) -> u32 { let points: u32 = match level { "mudah" => 10, "sulit" => 30, _ => return 0, // `return` bertipe `!`, jadi cocok di tempat yang mengharapkan u32 }; points * 2}
fn main() { println!("mudah: {}, sulit: {}, aneh: {}", level_points("mudah"), level_points("sulit"), level_points("aneh"));
let mut total = 0; for input in ["4", "oops", "6"] { let n: i32 = match input.parse() { Ok(n) => n, Err(_) => continue, // `continue` juga bertipe `!` }; total += n; } println!("total: {total}");
let config_file: Option<&str> = None; let config = match config_file { Some(path) => path, None => quit("file konfigurasi tidak ditemukan, berhenti"), }; println!("dimuat {config}"); // tidak pernah dicetak}Output:
mudah: 20, sulit: 60, aneh: 0total: 10file konfigurasi tidak ditemukan, berhentiSatu peringatan: process::exit menghentikan program saat itu juga, tanpa menjalankan kode pembersihan (destructor) untuk nilai-nilai yang masih hidup. Di program sungguhan, kembali dari main biasanya pilihan yang lebih halus.
Ada dua makro yang memakai ! sebagai penanda sementara saat kamu masih membuat sketsa kode. todo!() artinya “nanti aku tulis”. unimplemented!() artinya “ini tidak didukung di sini”. Keduanya akan panic kalau sampai dijalankan, tapi keduanya membuat sisa programmu tetap bisa di-compile:
fn export_pdf(report: &str) -> Vec<u8> { todo!("ekspor PDF menyusul di v2")}Kalau fungsi itu dipanggil, kamu akan mendapat not yet implemented: ekspor PDF menyusul di v2. Dengan unimplemented!("ekspor PDF") pesannya menjadi not implemented: ekspor PDF. Awalan not yet implemented dan not implemented berasal dari Rust, jadi selalu dalam bahasa Inggris.
Pakai ini kalau sebuah fungsi harus menghentikan program, atau sebuah cabang harus keluar lebih awal alih-alih memberi nilai.
Kemudahan kecil yang sangat membantu
Section titled “Kemudahan kecil yang sangat membantu”Ini alat-alat saku: kecil, tapi akan kamu pakai setiap minggu.
matches!(value, pattern)menjawab “apakah ini cocok dengan pattern-nya?” dengantrueataufalse(selengkapnya di halaman Pattern Matching).std::cmp::min/maxmemilih yang lebih kecil atau lebih besar dari dua nilai, dan.clamp(low, high)menjaga nilai tetap di dalam rentang, seperti tombol volume yang mentok di 100..abs_diff(other)memberi jarak antara dua angka. Hasilnya selalu positif dan tidak pernah overflow, bahkan untuk tipe unsigned.dbg!(expr)mencetak nama file, baris, ekspresi dan nilainya, lalu mengembalikan nilai itu, jadi kamu bisa membungkus apa saja dengannya.assert!(condition)akan panic kalau kondisinya false.debug_assert!melakukan hal yang sama, tapi hanya di build debug, jadi tidak ada biayanya di build release.
use std::cmp::{max, min};
enum Order { Pending, Shipped, Delivered, Cancelled,}
fn main() { let orders = [Order::Pending, Order::Delivered, Order::Shipped, Order::Cancelled, Order::Pending];
// matches!: pengecekan pattern ya/tidak, dengan | untuk "atau" let open = orders.iter().filter(|o| matches!(o, Order::Pending | Order::Shipped)).count(); println!("pesanan yang masih berjalan: {open}");
// min, max dan clamp println!("min {}, max {}", min(7, 3), max(7, 3)); let volume = 140; let safe = volume.clamp(0, 100); println!("volume {volume} dibatasi jadi {safe}");
// abs_diff: seberapa jauh jaraknya, tidak pernah negatif let (guess, secret): (u32, u32) = (30, 42); println!("tebakanmu meleset {}", guess.abs_diff(secret));
// dbg! mencetak ke stderr dan mengembalikan nilainya let doubled = dbg!(safe * 2);
// assert! menghentikan program kalau ini sampai false assert!(doubled <= 200, "volume ganda terlalu tinggi: {doubled}"); debug_assert!(safe <= 100); // hanya diperiksa di build debug println!("semua pemeriksaan lolos");}Output:
pesanan yang masih berjalan: 3min 3, max 7volume 140 dibatasi jadi 100tebakanmu meleset 12semua pemeriksaan lolosBaris dari dbg! tidak muncul di output di atas, karena dbg! menulis ke stderr (keluaran untuk error), bukan ke stdout. Di Playground, baris itu muncul di panel “Standard Error”:
[src/main.rs:28:19] safe * 2 = 200Itu juga alasan dbg! aman ditaburkan di mana-mana saat debugging: ia tidak tercampur dengan output asli programmu. Hanya saja, jangan lupa menghapusnya sebelum kodenya dirilis.
Pengurutan juga punya alat-alat kecilnya sendiri. Halaman Pengurutan membahasnya secara mendalam; berikut versi singkatnya. sort_unstable sedikit lebih cepat daripada sort dan cocok untuk angka biasa. sort_by_key mengurutkan berdasarkan satu field. binary_search menemukan item di data yang sudah terurut dengan sangat cepat, seperti membuka kamus langsung di tengah alih-alih membaca dari halaman pertama.
struct Player { name: &'static str, score: u32,}
fn main() { // sort_unstable: sedikit lebih cepat; item yang sama bisa bertukar tempat let mut ids = vec![42, 7, 19, 3, 25]; ids.sort_unstable(); println!("{ids:?}");
// binary_search: Ok(indeks) kalau ketemu, Err(indeks) tempat ia seharusnya berada println!("cari 19: {:?}", ids.binary_search(&19)); println!("cari 20: {:?}", ids.binary_search(&20));
// sort_by_key: urutkan berdasarkan satu field let mut players = vec![ Player { name: "Ana", score: 120 }, Player { name: "Ben", score: 95 }, Player { name: "Chen", score: 150 }, ]; players.sort_by_key(|p| p.score); let names: Vec<&str> = players.iter().map(|p| p.name).collect(); println!("terendah ke tertinggi: {names:?}");}Output:
[3, 7, 19, 25, 42]cari 19: Ok(2)cari 20: Err(3)terendah ke tertinggi: ["Ben", "Ana", "Chen"]Err(3) bukan kegagalan yang perlu dikhawatirkan. Artinya: 20 tidak ada di daftar, dan indeks 3 adalah tempat kamu bisa menyisipkannya supaya daftarnya tetap terurut.
Pakai ini kalau kamu sadar sedang menulis if atau perulangan kecil yang sebenarnya sudah dikerjakan oleh salah satu alat ini.
Kesalahan umum
Section titled “Kesalahan umum”Memakai mem::take pada tipe yang tidak punya Default. mem::take harus meninggalkan sesuatu, jadi ia perlu tahu seperti apa nilai “kosong” untuk tipe itu:
use std::mem;
struct Connection { host: String,}
struct Pool { active: Connection,}
impl Pool { fn reset(&mut self) -> Connection { mem::take(&mut self.active) // ❌ seperti apa Connection yang "kosong"? }}error[E0277]: the trait bound `Connection: Default` is not satisfied --> src/main.rs:13:19 |13 | mem::take(&mut self.active) // ❌ seperti apa Connection yang "kosong"? | --------- ^^^^^^^^^^^^^^^^ the trait `Default` is not implemented for `Connection` | | | required by a bound introduced by this call |help: consider annotating `Connection` with `#[derive(Default)]`Compiler bilang trait Default belum diimplementasikan untuk Connection. Perbaiki dengan memilih sendiri penggantinya lewat mem::replace(&mut self.active, Connection { host: "db2".to_string() }), atau dengan menyimpan field-nya sebagai Option<Connection> lalu memakai self.active.take(). Tambahkan #[derive(Default)] hanya kalau nilai “kosong” memang masuk akal untuk tipe itu.
Memakai ? pada Option di fungsi yang mengembalikan Result. Keduanya tidak otomatis bisa dicampur:
fn port_from(setting: Option<&str>) -> Result<u16, String> { let raw = setting?; // ❌ None tidak punya pesan error untuk dikembalikan raw.parse::<u16>().map_err(|e| e.to_string())}error[E0277]: the `?` operator can only be used on `Result`s, not `Option`s, in a function that returns `Result` --> src/main.rs:2:22 |1 | fn port_from(setting: Option<&str>) -> Result<u16, String> { | ---------------------------------------------------------- this function returns a `Result`2 | let raw = setting?; // ❌ None tidak punya pesan error untuk dikembalikan | ^ use `.ok_or(...)?` to provide an error compatible with `Result<u16, String>`Compiler bahkan sudah memberitahumu cara memperbaikinya: setting.ok_or("PORT belum diatur".to_string())?, atau ok_or_else seperti di contoh sebelumnya. Lihat E0277 untuk bentuk lain dari error ini.
Mengira dedup atau binary_search bekerja pada data yang belum terurut. dedup hanya menghapus pengulangan yang bersebelahan, dan binary_search pada data yang belum terurut memberi jawaban yang tidak berarti. Keduanya bukan error compiler, jadi gampang terlewat. Urutkan dulu datanya.
Lupa sisa dari chunks_exact. Perulangan for pada chunks_exact(3) diam-diam melewatkan 1 atau 2 item terakhir. Kalau item itu penting, tangani .remainder(), atau pakai chunks saja.
Latihan
Section titled “Latihan”1. Perubahan harga harian
Section titled “1. Perubahan harga harian”Kamu punya harga [100, 104, 98, 110, 107]. Buat sebuah Vec berisi perubahan dari tiap hari ke hari berikutnya, cetak, lalu cetak kenaikan terbesarnya. Petunjuk: windows(2) memberi setiap pasangan sebagai slice kecil w, jadi perubahannya adalah w[1] - w[0]. .iter().max() mencari yang terbesar.
Lihat solusi
fn main() { let prices = [100, 104, 98, 110, 107];
let changes: Vec<i32> = prices.windows(2).map(|w| w[1] - w[0]).collect(); println!("perubahan: {changes:?}");
let biggest_rise = changes.iter().max(); println!("kenaikan terbesar: {biggest_rise:?}");}Output:
perubahan: [4, -6, 12, -3]kenaikan terbesar: Some(12)Lima harga menghasilkan empat perubahan, karena windows(2) membuat satu pasangan di setiap langkah. max() mengembalikan Option, karena daftar kosong tidak punya nilai terbesar.
2. Kosongkan antrean printer
Section titled “2. Kosongkan antrean printer”Tulis struct Printer dengan field queue: Vec<String>. Beri method add(&mut self, file: &str) dan method flush(&mut self) -> Vec<String> yang menyerahkan semua file di antrean dan membiarkan antreannya kosong. Tambahkan dua file, panggil flush, lalu cetak apa saja yang dikeluarkan dan berapa file yang tersisa. Petunjuk: flush hanya punya &mut self, jadi pakai std::mem::take.
Lihat solusi
use std::mem;
struct Printer { queue: Vec<String>,}
impl Printer { fn add(&mut self, file: &str) { self.queue.push(file.to_string()); }
fn flush(&mut self) -> Vec<String> { mem::take(&mut self.queue) }}
fn main() { let mut printer = Printer { queue: Vec::new() }; printer.add("laporan.pdf"); printer.add("foto.png");
let jobs = printer.flush(); println!("mencetak {} tugas: {jobs:?}", jobs.len()); println!("sisa di antrean: {}", printer.queue.len());}Output:
mencetak 2 tugas: ["laporan.pdf", "foto.png"]sisa di antrean: 0Menulis self.queue begitu saja di flush akan gagal dengan E0507. mem::take memindahkan Vec-nya keluar dan meninggalkan Vec kosong sebagai gantinya, tanpa menyalin apa pun.
3. Port valid yang pertama
Section titled “3. Port valid yang pertama”Tulis fungsi first_port(candidates: &[&str]) -> Option<u16> yang mengembalikan string pertama yang bisa di-parse sebagai u16. Panggil fungsi itu dengan ["", "abc", "70000", "8080", "9090"] dan dengan ["none", "-1"], lalu cetak port-nya, atau 3000 kalau tidak ada. Petunjuk: find_map dengan s.parse::<u16>().ok(), lalu unwrap_or(3000).
Lihat solusi
fn first_port(candidates: &[&str]) -> Option<u16> { candidates.iter().find_map(|s| s.parse::<u16>().ok())}
fn main() { let from_config = ["", "abc", "70000", "8080", "9090"]; let all_bad = ["none", "-1"];
println!("memakai port {}", first_port(&from_config).unwrap_or(3000)); println!("memakai port {}", first_port(&all_bad).unwrap_or(3000));}Output:
memakai port 8080memakai port 3000"70000" memang kelihatan seperti angka, tapi tidak muat di u16 (nilai terbesarnya 65535), jadi parse-nya gagal dan find_map lanjut ke item berikutnya. Ia berhenti di "8080" dan bahkan tidak pernah melihat "9090".
Ringkasan
Section titled “Ringkasan”mem::take,mem::replacedanOption::takememindahkan nilai keluar dari&mutdengan meninggalkan sesuatu di tempatnya;mem::swapmenukar dua nilai.- Pada slice,
windowsmemberi tetangga yang saling tumpang tindih,chunksmemberi kelompok, dansplit_first/split_lastmemberi “satu item dan sisanya”. peekablemembuatmu bisa mengintip ke depan;zip,enumerate,chain,fold,find_mapdanpartitionmenggantikan banyak perulangan yang ditulis manual.- Helper
Optionsepertiunwrap_or_default,map_or,filterdanok_ormenyelamatkanmu dari menulismatchkecil-kecil, dan?juga bisa dipakai padaOption. entry()denganor_insert_withatauand_modifymemperbaruiHashMaphanya dengan satu kali pencarian.- Never type
!dimiliki kode yang tidak pernah selesai (panic!,return,continue,process::exit), jadi ia cocok di mana saja sebuah nilai diharapkan.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Apa yang ditinggalkan mem::take(&mut self.items) kalau items adalah sebuah Vec?
2. Apa yang kamu dapat dari [1, 2, 3, 4].windows(2)?
3. Kenapa let x: i32 = match s { "a" => 1, _ => return }; bisa di-compile?
4. Fungsimu mengembalikan Result, tapi yang kamu punya sebuah Option. Apa yang kamu tambahkan sebelum ?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”std::mem— dokumentasi pustaka standar (bahasa Inggris):take,replace,swapdan helper memori lainnya, lengkap dengan contoh.- Method slice — dokumentasi pustaka standar (bahasa Inggris): semua method slice, termasuk
windows,chunks,split_firstdanbinary_search. - Trait
Iterator— dokumentasi pustaka standar (bahasa Inggris): daftar lengkap method iterator, darizipdanpeekablesampaipartitiondanfind_map. Option— dokumentasi pustaka standar (bahasa Inggris): semua helperOption, dikelompokkan berdasarkan fungsinya.- Diverging functions — Rust by Example (bahasa Inggris): contoh-contoh singkat fungsi yang mengembalikan
!dan kenapa itu berguna.