println!, vec!, dan format! telah kita pakai sejak Chapter pertama tanpa pernah mempertanyakan bentuk pemanggilannya. Setelah dua Chapter terakhir, kita kini punya alat untuk membedahnya: pemanggilan println!("{} dan {}", a, b) adalah pemanggilan macro dengan jumlah argumen variabel, vec![0; 5] adalah pemanggilan macro dengan bentuk sintaks khusus, dan format! adalah macro yang mengembalikan nilai String. Chapter ini membalik pandangan tersebut menjadi pemahaman: mengapa ketiganya mustahil menjadi fungsi, bagaimana arm dan repetisi menyusun antarmukanya, dan bagaimana println! bekerja di atas mesin bernama format_args!.

println! — Arity Bebas dan Format yang Diperiksa Saat Kompilasi #

Alasan pertama println! berbentuk macro adalah arity: pemanggilan yang sah berkisar dari nol argumen (println!() yang sekadar mencetak baris baru) hingga belasan argumen. Fungsi tidak memiliki cara mendefinisikan antarmuka seperti itu, seperti yang dibuktikan galat E0061 pada Chapter Mengapa Macro. Alasan keduanya lebih tajam: pencocokan antara format string dan argumen diperiksa saat kompilasi. Jika kita menulis println!("{} {}", harga) — dua penampung tetapi satu argumen — kompilator menolak program dengan galat 2 positional arguments in format string, but there is 1 argument. Bandingkan dengan printf bahasa C: pencocokan %s dan %d dengan argumen tidak diperiksa sama sekali, sehingga kelebihan penentu format berubah menjadi cetakan sampah atau crash saat program berjalan.

Program berikut memperagakan rentang kemampuan println!, dari arity nol hingga penentu format rata kiri, rata kanan, angka desimal, dan penampung berindeks:

rust
fn main() {
    let nama = "Kopi Gayo 250g";
    let harga = 85000;
    let stok = 7;

    println!();
    println!("== Daftar Menu ==");
    println!("{} berharga Rp{}", nama, harga);
    println!("{:<14}|{:>6}|", "Produk", "Harga");
    println!("{:<14}|{:>6}|", nama, harga);
    println!("Sisa stok {} pack, rata-rata terjual {:.1} per hari", stok, 2.5);
    println!("{0} dan {1}, lalu {0} lagi", nama, stok);
}

Output yang Dihasilkan:

text

== Daftar Menu ==
Kopi Gayo 250g berharga Rp85000
Produk        | Harga|
Kopi Gayo 250g| 85000|
Sisa stok 7 pack, rata-rata terjual 2.5 per hari
Kopi Gayo 250g dan 7, lalu Kopi Gayo 250g lagi

Mari kita bedah pemanggilan-pemanggilan pentingnya:

  1. Baris 6 (println!();): baris pertama output memang kosong — pemanggilan tanpa argumen hanya mencetak satu baris baru. Inilah arity nol yang tidak tersedia bagi fungsi dengan parameter.
  2. Baris 8 (println!("{} berharga Rp{}", nama, harga);): dua penampung {} diisi dua argumen secara berurutan. Kompilator memeriksa jumlah penampung terhadap jumlah argumen sebelum program berjalan.
  3. Baris 9-10 ({:<14} dan {:>6}): penentu format mengatur tampilan — < rata kiri pada lebar minimal 14, > rata kanan pada lebar minimal 6. Teks "Produk" dipadatkan spasi sampai 14 karakter, "Harga" digeser kanan sampai lebar 6. Angka dan teks sama-sama diterima karena macro memeriksa tipe tiap argumen terhadap penentu formatnya saat kompilasi.
  4. Baris 11 ({:.1}): penentu desimal membulatkan 2.5 ke satu angka di belakang koma.
  5. Baris 12 ({0} dan {1}): penampung berindeks memakai ulang argumen — indeks 0 merujuk nama sehingga nama tercetak dua kali dari satu argumen.

format_args! sebagai Mesin di Balik println! #

println! tidak mencetak sendiri. Secara konsep, ekspansi println! mengerjakan dua langkah: memanggil macro format_args! untuk membangun struct Arguments yang merangkai format string beserta referensi argumen, lalu menyerahkan struct tersebut ke fungsi internal pustaka standar yang menuliskannya ke stdout. Kita bisa mengamati mesin tersebut secara langsung karena format_args! mengembalikan nilai Arguments yang mengimplementasikan Display:

rust
fn main() {
    let nama = "Kopi Gayo 250g";
    let harga = 85000;
    let argumen = format_args!("Ringkas: {} Rp{}", nama, harga);

    println!("{}", argumen);
}

Output yang Dihasilkan:

text
Ringkas: Kopi Gayo 250g Rp85000

Nilai argumen bukan String hasil penggabungan — Arguments hanya menampung potongan referensi, sehingga pembuatannya tidak mengalokasikan memori baru. println! mewarisi desain murah ini: pencetakan langsung menulis ke stdout tanpa perantara String.

vec! — Dua Bentuk Panggilan yang Hanya Mungkin untuk Macro #

vec! melayani dua bentuk pemanggilan yang sama sekali tidak tersedia bagi fungsi. Bentuk pertama adalah daftar elemen dengan panjang bebas: vec![15000, 22000, 18000] — daftar ini dicocokkan lewat repetisi $(...),+ seperti my_vec! pada Chapter Repetisi di Macro. Bentuk kedua adalah sintaks pengulangan vec![0; 5] — tanda titik koma di dalam argumen bukan pemisah argumen fungsi, melainkan bagian pola macro yang memasangkan nilai elemen dengan jumlah pengulangan. Fungsi tidak punya cara menerima bentuk x; n sebagai satu konstruksi sintaks.

rust
fn main() {
    let daftar_harga = vec![15000, 22000, 18000];
    let penanda = vec![0; 5];

    println!("Daftar harga: {:?}", daftar_harga);
    println!("Penanda: {:?}", penanda);
    println!("Total harga: Rp{}", daftar_harga.iter().sum::<i32>());
}

Output yang Dihasilkan:

text
Daftar harga: [15000, 22000, 18000]
Penanda: [0, 0, 0, 0, 0]
Total harga: Rp55000

Bedah singkat kedua bentuknya:

  1. Baris 2 (vec![15000, 22000, 18000]): repetisi menangkap tiga ekspresi, lalu ekspansi membangun Vec berisi ketiganya — mekanisme yang sama dengan my_vec!, hanya dengan implementasi pustaka yang lebih efisien.
  2. Baris 3 (vec![0; 5]): arm terpisah menangkap pola $elem:expr; $n:expr dan menghasilkan Vec berisi lima salinan nilai 0.
  3. Baris 5 ({:?}): penentu {:?} memakai trait Debug untuk mencetak representasi struktur — tipe koleksi bawaan sudah mengimplementasikan Debug, dan {:?} sendiri juga layak diperiksa kompilator terhadap argumennya.

format! — Mesin yang Sama, Hasil Berupa String #

format! memakai mesin format yang identik dengan println!, tetapi alih-alih menulis ke stdout, macro ini membangun String dan mengembalikannya sebagai nilai:

rust
fn main() {
    let nama = "Kopi Gayo 250g";
    let harga = 85000;
    let info = format!("{} Rp{} (sisa {} pack)", nama, harga, 7);

    println!("Info: {}", info);
    println!("Panjang info: {} karakter", info.len());
}

Output yang Dihasilkan:

text
Info: Kopi Gayo 250g Rp85000 (sisa 7 pack)
Panjang info: 36 karakter

Hasil format! adalah String biasa: method len, penyambungan, dan pembandingan teks bekerja di atasnya seperti pada String hasil String::from. Ketika kita membutuhkan teks terformat untuk disimpan di variabel, dikirim ke log, atau digabung ke pesan lain, format! adalah pilihannya; ketika teks langsung dicetak, println! menghemat satu langkah alokasi.

Perbandingan dengan printf di C #

Tabel berikut merangkum pembedaan yang menjelaskan keputusan desain ketiganya:

Aspekprintf (C)println! (Rust)
Jumlah argumenVariadic tanpa pemeriksaanVariadic melalui arm macro
Penentu format%s, %d, ditafsir saat runtime{}, {:?}, diperiksa saat kompilasi
Kesalahan tipe argumenPerilaku tak terdefinisi atau cetakan salahProgram gagal dikompilasi
Bentuk pemanggilan khususTidak ada — selalu daftar argumenBisa: vec![0; 5], blok, identifier

Kolom terakhir menunjuk hal yang lebih luas dari sekadar format: karena macro mencocokkan token, antarmuka boleh berbentuk sintaks apa pun yang polanya sanggup tangkap — kebebasan yang tidak dimiliki mekanisme pemanggilan fungsi.

Ringkasan #

println!, vec!, dan format! adalah penerapan langsung dari seluruh mekanisme yang kita pelajari pada dua Chapter sebelumnya: arity variabel lewat arm, daftar elemen lewat repetisi $(...),+, bentuk sintaks khusus lewat token harfiah dalam pola, serta pemeriksaan tipe dan format yang tetap berjalan saat kompilasi. println! bekerja di atas format_args! yang merangkai format string dengan referensi argumen tanpa alokasi, sementara format! memakai mesin yang sama untuk menghasilkan String. Perbandingan dengan printf memperlihatkan faedah desainnya: kesalahan yang di C menunggu runtime, di Rust dimatikan sebelum program lahir. Satu wilayah macro masih belum tersentuh: macro yang ditulis sebagai fungsi Rust sungguhan yang berjalan di dalam kompilator — macro prosedural, pembahasan penutup Section ini.