Buka proyek yang ditulis lima orang dan kita akan menemukan lima gaya: satu orang menempelkan titik koma, satu lagi menambah spasi di sekitar operator, yang lain menggeser indentasi dua spasi. Semua variasi itu dikompilasi dengan baik oleh compiler — Rust tidak peduli bentuk — tetapi manusia peduli: review code jadi penuh komentar soal spasi, dan membaca file orang lain menuntut penyesuaian terus-menerus. Ekosistem Rust menghapus kelas perdebatan ini dengan satu keputusan: formatter resmi bernama rustfmt, dijalankan lewat perintah cargo fmt, yang menulis ulang seluruh proyek ke satu gaya baku. Section Kualitas Kode dibuka dengan alat ini karena pengaruhnya paling langsung dan paling terasa setiap hari, sebelum kita melanjutkan ke linter clippy dan otomatisasi CI.
Masalah: Kode yang Berjalan tetapi Sulit Dibaca #
Compiler menerima kode apapun bentuknya — indentasi kacau, spasi tak konsisten, dan blok {} yang menempel pada apa pun tetap dikompilasi selama sintaksnya valid. Masalahnya muncul setelah kompilasi: pembaca manusia harus menelusuri kode dengan struktur visual yang tidak jelas. Perhatikan bahwa semua aturan gaya yang kita bahas di sini tidak memengaruhi perilaku program sama sekali; masalahnya murni pada keterbacaan dan konsistensi antar kontributor. Itulah alasan solusinya boleh agresif: memformat ulang seluruh file secara otomatis tanpa menanya.
Mekanisme: Parse AST, Cetak Ulang #
rustfmt bukan pengganti teks yang bekerja pola per pola. Alat ini mem-parse source code menjadi sintaks abstrak (AST) — struktur pohon yang merekam maksud kode tanpa format aslinya — lalu mencetak ulang pohon tersebut dengan printer gaya baku: indentasi empat spasi, spasi di sekitar operator, baris dibungkus pada lebar 100 karakter, dan koma terakhir pada literal yang multi-baris. Karena yang dicetak ulang adalah AST, hasil pemformatan dijamin setara secara semantik dengan kode asal — nama, tipe, dan alur eksekusi tidak berubah, hanya bentuknya. Konsekuensi lainnya: rustfmt idempoten — menjalankan cargo fmt pada file yang sudah terformat tidak mengubah apa pun, karena output printer selalu sama untuk satu AST.
Contoh: Memformat Proyek yang Berantakan #
Buat proyek baru, lalu sengaja isi src/main.rs dengan kode yang berfungsi tetapi hancur formatnya:
use std::collections::HashMap;
// catatan: nama karyawan dan jumlah hari hadir
fn ringkasan_absensi(kehadiran:&HashMap<String,u32>,total_hari:u32)->HashMap<String,String>{
let mut hasil=HashMap::new();
for(nama,hari) in kehadiran{
let persen=*hari as f64/total_hari as f64*100.0;
let status=if persen>=90.0{"sangat rajin"}else if persen>=75.0{"rajin"}else{"perlu bimbingan"};
hasil.insert(nama.clone(),format!("{} hari ({}%) — status: {}",hari,persen as u32,status));
}
hasil
}
fn main(){
let mut kehadiran=HashMap::new();
kehadiran.insert(String::from("Budi Santoso"),24u32);
kehadiran.insert(String::from("Siti Aminah"),22u32);
let ringkasan=ringkasan_absensi(&kehadiran,25);
for(nama,info) in &ringkasan{
println!("{}: {}",nama,info);
}}Kode tersebut dikompilasi dan berjalan — mari kita catat dulu outputnya sebagai pembanding:
cargo runOutput yang Dihasilkan:
Budi Santoso: 24 hari (96%) — status: sangat rajin
Siti Aminah: 22 hari (88%) — status: rajinSekarang jalankan formatter untuk seluruh proyek:
cargo fmtPerintah tersebut memformat semua file .rs di dalam src/ sekaligus. Hasil src/main.rs setelah pemformatan:
use std::collections::HashMap;
// catatan: nama karyawan dan jumlah hari hadir
fn ringkasan_absensi(kehadiran: &HashMap<String, u32>, total_hari: u32) -> HashMap<String, String> {
let mut hasil = HashMap::new();
for (nama, hari) in kehadiran {
let persen = *hari as f64 / total_hari as f64 * 100.0;
let status = if persen >= 90.0 {
"sangat rajin"
} else if persen >= 75.0 {
"rajin"
} else {
"perlu bimbingan"
};
hasil.insert(
nama.clone(),
format!("{} hari ({}%) — status: {}", hari, persen as u32, status),
);
}
hasil
}
fn main() {
let mut kehadiran = HashMap::new();
kehadiran.insert(String::from("Budi Santoso"), 24u32);
kehadiran.insert(String::from("Siti Aminah"), 22u32);
let ringkasan = ringkasan_absensi(&kehadiran, 25);
for (nama, info) in &ringkasan {
println!("{}: {}", nama, info);
}
}Mari kita bedah perubahan yang dilakukan rustfmt:
- Baris 4 (
fn ringkasan_absensi(kehadiran: &HashMap<String, u32>, ...)): tanda koma kini diikuti spasi,:pada tipe dipisah spasi, dan->diberi spasi di kedua sisi. Tanda{tetap menempel pada tanda tangan — gaya baku rustfmt. - Baris 5-15 (badan fungsi
ringkasan_absensi): seluruh statement kini diindentasi empat spasi dan sejajar dengan cermat. Kode asli tidak punya indentasi sama sekali; AST-nya identik, hanya cetakannya yang berbeda. - Baris 9-15 (rantaian
if persen >= 90.0 { ... } else if ... else { ... }): expressionifyang semula dipadatkan satu baris dibentangkan multi-baris karena gaya baku tidak mengizinkan blok{...}berisi nilai pada baris yang sama dengan kondisi. - Baris 16-19 (
hasil.insert(nama.clone(), format!(...))): pemanggilan yang melebihi lebar 100 karakter dipecah argumen per baris dengan indentasi berjenjang. - Baris 21 (
fn main() {):{kini dipisah spasi darimain(), dan seluruh badan main diindentasi empat spasi;}}kembar di akhir file asli dibedakan menjadi}milik loop dan}milik fungsi. - Baris komentar (
// catatan: ...): komentar baris dibiarkan apa adanya — rustfmt memindahkan posisi barisnya bila perlu, tetapi tidak pernah menulis ulang isi komentar.
Bukti terpenting: jalankan program yang sudah diformat dan bandingkan dengan output sebelumnya:
cargo runOutput yang Dihasilkan:
Budi Santoso: 24 hari (96%) — status: sangat rajin
Siti Aminah: 22 hari (88%) — status: rajinOutput identik dengan sebelum pemformatan — perilaku program tidak berubah sama sekali, hanya bentuknya yang kini baku.
Memeriksa Tanpa Mengubah: cargo fmt –check #
Dalam kerja tim kita sering perlu memeriksa apakah sebuah file sudah terformat tanpa menulis ulangnya — misalnya saat review atau saat pemeriksaan di server. Opsi --check menjawabnya:
cargo fmt --checkOutput yang Dihasilkan (pada kode yang belum terformat, kutipan):
Diff in /tmp/w5b-fmt/src/main.rs:1:
use std::collections::HashMap;
// catatan: nama karyawan dan jumlah hari hadir
-fn ringkasan_absensi(kehadiran:&HashMap<String,u32>,total_hari:u32)->HashMap<String,String>{
-let mut hasil=HashMap::new();
-for(nama,hari) in kehadiran{
+fn ringkasan_absensi(kehadiran: &HashMap<String, u32>, total_hari: u32) -> HashMap<String, String> {
+ let mut hasil = HashMap::new();
+ for (nama, hari) in kehadiran {Perintah ini tidak mengubah file; ia hanya mencetak diff baris yang akan diubah — baris dengan prefiks - akan diganti baris dengan prefiks + — lalu keluar dengan status gagal (exit code bukan nol) bila ada perbedaan. Pada kode yang sudah terformat, cargo fmt --check tidak mencetak apa pun dan keluar dengan status sukses. Perilaku “gagal bila tidak terformat” inilah yang nanti kita tanam sebagai langkah wajib di CI pada Chapter Integrasi CI.
Ringkasan #
cargo fmt mem-format seluruh file Rust di proyek sekaligus dengan cara mem-parse AST dan mencetak ulang gaya baku: indentasi empat spasi, lebar baris 100, spasi di sekitar operator, dan blok yang dibentangkan. Perilaku program dijamin tidak berubah — output program sebelum dan sesudah pemformatan identik — dan pemformatan idempoten. Opsi cargo fmt --check memeriksa tanpa mengubah dan gagal bila ada file yang belum terformat, menjadikannya kandidat penjaga gerbang di CI. Gaya baku ini bisa disesuaikan lewat file rustfmt.toml, materi Chapter berikutnya.