Setelah LTO, biner penghitung prima kita menyusut ke 366 KB — jauh lebih ramping dari 4,4 MB versi dev, tetapi masih terasa gemuk untuk sebuah CLI yang hanya menghitung bilangan. Sisa bobot itu bukan kode program, melainkan penumpang senyap: symbol table yang memetakan nama semua fungsi, dan mesin unwinding yang bersiap mengurai stack saat panic terjadi. Kedua penumpang tersebut tidak dibutuhkan biner yang berjalan normal di produksi, dan Cargo menyediakan dua tuas profil untuk menurunkannya: strip untuk symbol dan panic = "abort" untuk unwinding. Pada chapter ini kita bedah isi biner lebih dulu agar keputusan pemangkasan berdasarkan bukti, lalu ukur hasilnya dengan ls -lh, dan tutup dengan harga yang harus dibayar ketika produksi bermasalah.

Apa Sebenarnya Ada di Dalam Biner #

Sejak Cargo versi 1.77, profil release secara bawaan sudah menjalankan strip = "debuginfo" — informasi debug DWARF yang memetakan instruksi mesin ke baris sumber ikut dibuang otomatis. Kita bisa membuktikannya sendiri: objdump -h pada biner dev menemukan delapan section bernama .debug_*, sementara biner release menemukan nol. Inilah alasan utama biner release hanya 444 KB padahal biner dev 4,4 MB.

Akan tetapi strip = "debuginfo" tidak menyentuh symbol table — tabel nama seluruh fungsi dan variabel statis, termasuk milik pustaka standar. Pada biner release bawaan, tabel itu masih hidup suburnya:

bash
nm target/release/penghitung-prima | wc -l
file target/release/penghitung-prima

Output yang Dihasilkan:

text
1102
target/release/penghitung-prima: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=8a5dc4e28e8dc224e986e2c03115b7e1f49fb0a2, not stripped

Terdapat 1102 symbol di dalam biner milik program yang hanya punya satu fungsi buatan kita sendiri. Perintah file menegaskan status not stripped. Nilai BuildID adalah sidik jari build yang berbeda pada setiap kompilasi, dan jumlah symbol akan sedikit berbeda antar mesin; keluaran di atas diambil dari satu eksekusi nyata. Symbol-symbol inilah yang akan kita buang, dan nm sekaligus menjadi alat bukti bahwa pemangkasan benar-benar terjadi.

Membuang Symbol Table dengan strip #

Cargo menerima tiga nilai untuk kunci strip: "none" membiarkan semuanya, "debuginfo" (bawaan release modern) membuang DWARF saja, dan "symbols" membuang DWARF sekaligus seluruh symbol table. Kita pakai nilai paling agresif pada profil release:

toml
[profile.release]
strip = "symbols"
  1. Baris 1 ([profile.release]): tabel pengaturan profil release di Cargo.toml — pada workspace, tabel ini wajib berada di Cargo.toml root agar tidak diabaikan, seperti dibahas pada chapter LTO.
  2. Baris 2 (strip = "symbols"): memerintahkan Cargo meneruskan strip=symbols ke rustc, sehingga linker mengeluarkan symbol table dan debuginfo dari biner jadi.

Mari kita bangun ulang dan periksa hasilnya:

bash
cargo build --release
ls -lh target/release/penghitung-prima
./target/release/penghitung-prima
file target/release/penghitung-prima

Output yang Dihasilkan:

text
    Finished `release` profile [optimized] target(s) in 0.09s
-rwxrwxr-x 2 z z 337K Oct 13 23:11 target/release/penghitung-prima
Jumlah prima sampai 1000000: 78498
Waktu eksekusi: 116.510871ms
target/release/penghitung-prima: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=7b21048ec6550b1965541ac390139eec9b8984d8, stripped

Biner mengecil dari 444 KB menjadi 337 KB — pemangkasan 24 persen tanpa satu baris kode pun berubah. Status dari file berubah dari not stripped menjadi stripped, dan bila kita menjalankan nm target/release/penghitung-prima, nm kini menjawab no symbols. Program tetap berjalan normal dengan waktu eksekusi yang tidak berubah, karena symbol table memang tidak pernah dibaca oleh prosesor yang mengeksekusi kode — tabel itu murni alat bantu manusia dan debugger.

Kita juga bisa memangkas biner yang sudah terlanjur dibangun tanpa mengubah Cargo.toml, memakai perintah strip dari binutils:

bash
cp target/release/penghitung-prima /tmp/penghitung-prima-salinan
strip /tmp/penghitung-prima-salinan
ls -lh /tmp/penghitung-prima-salinan

Output yang Dihasilkan:

text
-rwxrwxr-x 1 z z 444K Oct 13 23:26 /tmp/penghitung-prima-salinan
-rwxrwxr-x 1 z z 337K Oct 13 23:26 /tmp/penghitung-prima-salinan

Dua baris ls dikeluarkan sebelum dan sesudah strip: salinan 444 KB mengecil menjadi 337 KB, persis hasil strip = "symbols" di profil, dan nm atas salinan tersebut juga menjawab no symbols. Perintah cp dipakai agar biner asli di target/ tetap utuh untuk di-debug. Cara profil lebih unggul karena terdokumentasi di Cargo.toml dan berlaku otomatis pada setiap build, sementara perintah strip berguna untuk biner lama yang tidak mau kita bangun ulang.

panic = “abort” dan Mesin Unwinding #

Tuas kedua menyingkirkan mesin stack unwinding. Secara bawaan, panic di Rust tidak langsung mematikan proses: runtime menjalankan unwinding yang mengurai stack frame demi frame, menjalankan destructor objek yang dilewati, dan menyerahkan kontrol ke catch_unwind bila ada. Mekanisme ini membutuhkan metadata unwinding dan pustaka pendukung di dalam biner. Nilai panic = "abort" mengganti strategi: panic tetap mencetak pesannya, lalu proses langsung memanggil abort() — tidak ada unwinding, tidak ada destructor yang dijalankan, dan tidak ada penyelamatan oleh catch_unwind.

toml
[profile.release]
strip = "symbols"
panic = "abort"
  1. Baris 3 (panic = "abort"): panic di profil release tidak lagi unwind melainkan langsung membatalkan proses; metadata unwinding boleh dibuang dari biner.

Pada pengukuran kami, kombinasi ini memangkas biner penghitung prima dari 337 KB menjadi 336 KB, dan panic = "abort" sendirian tanpa strip hanya memangkas 2 KB (444 KB menjadi 442 KB) karena mesin unwinding pustaka standar memang ringkas. Program besar dengan banyak dependensi biasanya memotong jauh lebih banyak, sehingga angka Anda akan berbeda. Beda yang lebih terasa justru muncul pada perilaku saat panic. Program kecil berikut sengaja mengakses indeks di luar batas:

rust
fn main() {
    let keranjang: Vec<&str> = vec!["apel", "jeruk"];
    println!("Elemen ke-100: {}", keranjang[100]);
}

Output yang Dihasilkan (bawaan, panic = “unwind”):

text
thread 'main' (28131) panicked at src/main.rs:3:44:
index out of bounds: the len is 2 but the index is 100
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Output yang Dihasilkan (panic = “abort”):

text
thread 'main' (28162) panicked at src/main.rs:3:44:
index out of bounds: the len is 2 but the index is 100
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Aborted

Pesan paniknya identik — angka dalam tanda kurung adalah ID thread yang berbeda pada setiap eksekusi. Bedanya ada di akhir cerita: mode unwind mengakhiri proses dengan exit code 101 setelah stack terurai rapi, sedangkan mode abort mengakhiri dengan sinyal SIGABRT (exit code 134) tanpa mengurai apa pun. Teks Aborted pada contoh terakhir dicetak oleh shell yang menjalankan program, bukan oleh program itu sendiri.

Harga dari Biner yang Dikikis #

Symbol dan metadata debug adalah bahan bakar backtrace. Saat produksi bermasalah, kita menjalankan RUST_BACKTRACE=1 dan berharap melihat nama fungsi beserta barisnya. Pada biner biasa, harapan itu terpenuhi:

bash
RUST_BACKTRACE=1 ./target/release/keranjang

Output yang Dihasilkan (biner tanpa strip):

text
thread 'main' (28228) panicked at src/main.rs:3:44:
index out of bounds: the len is 2 but the index is 100
stack backtrace:
   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::panic_bounds_check
   3: keranjang::main
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Empat frame muncul dengan nama fungsinya, termasuk keranjang::main milik kita. Pada biner yang di-strip symbol, ceritanya berubah total:

Output yang Dihasilkan (biner strip = “symbols”):

text
thread 'main' (28194) panicked at src/main.rs:3:44:
index out of bounds: the len is 2 but the index is 100
stack backtrace:
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Baris stack backtrace: muncul kosong — runtime tidak punya nama untuk ditampilkan karena tabelnya sudah dibuang. Praktik industri mengatasinya dengan membangun dua artefak dari source dan commit yang sama: biner strip yang dikirim ke produksi, dan biner kembar tanpa strip yang diarsipkan untuk membaca laporan kepanikan. Karena keduanya dibangun dari kode identik, alamat instruksi di dalamnya sama sehingga alamat mentah dari biner produksi bisa diterjemahkan memakai biner arsip.

Ringkasan #

Biner release modern sudah otomatis bebas debuginfo, tetapi masih menggendong lebih dari seribu symbol dan mesin unwinding. Kombinasi strip = "symbols" dan panic = "abort" memangkas biner contoh kita dari 444 KB ke 336 KB, mengubah exit code panic dari 101 menjadi 134, serta mengosongkan backtrace. Pemangkasan symbol aman bagi performa karena tabelnya tidak pernah dieksekusi, sedangkan pemangkasan unwinding mengubah kontrak kegagalan sehingga wajib diuji ulang. Biner ramping ini kini siap kita bawa melintasi perbatasan sistem operasi — tugas chapter Cross Compilation.