Bagi system programmer atau programmer yang menggunakan bahasa yang didesain untuk sistem seperti C, C++, dkk, mungkin sudah tidak asing dengan istilah dynamic dan static linking. Ya, sebuah program disebut statis penuh (fully static) apabila tidak memilki dependensi berupa pustaka dari luar, seperti file dengan ekstensi .dll (Windows), .dylib (Darwin), dan .so (Linux). Berbeda dengan program dynamic yang membutuhkan dependensi dari luar (external library).

Kita bisa memilih untuk mengkompilasi sebuah program secara dinamis atau statis penuh, namun kita perlu tahu terlebih dahulu apa kelebihan dan kekurangannya. Program dinamis memiliki kelebihan dengan ukurannya yang relative kecil, sehingga tidak memakan banyak spasi ketika diinstall karena program dinamis tidak membawa keseluruhan komponennya tetapi menggunakan komponen/fungsi dari pustaka luar (external library), namun kekurangannya adalah program jadi tidak environmentally portable, maksudnya tempat dimana program berada harus sudah tersedia pustaka yang dibutuhkan untuk agar program bisa berjalan, dan... harus kompatibel versinya, beda versi biasanya sudah tidak bisa jalan, sebagai contoh programmer C++ di Windows terutama yang menggunakan Visual Studio perlu di sistem operasinya telah tersedia MSVCRT (C++ Runtime dari Microsoft), tanpa komponen tersebut program mustahil bisa berjalan, untuk itulah beberapa orang memilih mengkompilasi programnya secara statis agar programnya bisa berjalan tanpa ketergantungan pustaka dari luar, ada juga yang memilih tetap membuatnya dinamis dengan menyertakan komponen yang dibutuhkan di dalam aplikasi installer-nya.

Kelebihan dari program yang dikompilasi secara statis adalah programnya lebih portabel, tidak membutuhkan komponen tertentu harus terpasang di sistem operasi yang akan dijalankan, yang penting arsitektur mesinnya cocok, kekurangannya adalah ukurannya yang lebih besar dibandingkan dengan yang dinamis, walaupun pada jaman sekarang masalah ukuran sudah tidak lagi perlu dikhawatirkan, karena selisihnya hanya beberapa mega sampai puluh mega bytes saja, namun jaman ketika floppy disk masih menjadi idola ini masalah besar dan serius, sekarang tidak lagi, kecuali Anda membuat untuk sistem benam (embedded system).

Bagi Anda programmer Go mungkin sadar bahwa hasil output binarinya adalah statis penuh, ya, karena dari awal Go memang didesain untuk menjadi portabel, ketika saya pertama kali menggunakan bahasa Go kurang lebih sekitar 7 tahun yang lalu hasil kompilasinya saya lihat statis penuh (fully static), bahkan tidak menggunakan System runtime seperti /usr/lib/libSystem.B.dylib di OSX, benar-benar murni tanpa dependensi, entah kalau sekarang, belum coba lagi saya. Sementara untuk programmer Rust pasti akan sadar bahwa secara bawaan (default) untuk aplikasi "hello world" saja ada dependensinya:

$ otool -L target/debug/hello
target/debug/hello:
    /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1252.50.4)
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)

Ya memang Rust tidak didesain untuk membuat aplikasi yang sangat portabel, namun saya sebagai seorang programmer sistem tidak merasa nyaman ketika sebuah aplikasi tidak bisa dibuat secara statis penuh, lalu kenapa tidak menggunakan Go saja? Ada 1001 alasan mengapa saya memilih Rust daripada Go, tapi saya tidak akan membahasnya di sini, mungkin di artikel lain dan di waktu yang lebih tepat ketika emosi sedang tidak mendidih seperti di suhu politik pilpres 2019 ini :D, karena mungkin bisa saja jadi language war :D

Musl Sebagai Penyelamat

Musl adalah pustaka standar (std lib) yang didesain dari awal untuk membuat program statis. Rust bisa menggunakan pustaka musl ini untuk membuat program yang sepenuhnya statis, sayangnya ini hanya untuk Linux dan Unix like operating sistem, saya belum pernah mencobanya di Windows.

Yang perlu Anda lakukan untuk melakukan kompilasi Rust secara statis penuh adalah dengan mengkompilasinya menggunakan toolchain Rust target *-unknown-linux-musl, sebagai contoh di sistem saya ada beberapa target musl:

$ rustup target list | grep musl
aarch64-unknown-linux-musl
arm-unknown-linux-musleabi
arm-unknown-linux-musleabihf
armv5te-unknown-linux-musleabi
armv7-unknown-linux-musleabihf
i586-unknown-linux-musl
i686-unknown-linux-musl
mips-unknown-linux-musl
mipsel-unknown-linux-musl
x86_64-unknown-linux-musl

Bisa dilihat Anda bisa membuat binari statis penuh untuk sistem benam dengan arsitektur prosesor ARM, MIPS, dll. Namun untuk keperluan saya, saya hanya akan mencontohkan membuat untuk mesin x86_64, sehingga saya memilih x86_64-unknown-linux-musl.

Anda bisa mulai menggunakan target tersebut dengan terlebih dahulu menginstallnya:

$ rustup target add x86_64-unknown-linux-musl

Lalu mengkompilasi program Anda dengan:

$ cargo build --target x86_64-unknown-linux-musl

Sudah? Selesai? Ya, namun apabila Anda menggunakan dependensi C seperti OpenSSL dan atau PostgreSQL lib :

Tidak semudah itu Ferguso

Ingat, yang ingin kita buat statis penuh (fully static), bukan statis parsial, sehingga ketika Anda mencoba melakukan kompilasi program Rust yang menggunakan dependensi seperti OpenSSL Anda masih bisa melihat output dari ldd (tool untuk melihat dependensi dari sebuah binari di Linux) masih mencantumkan librari OpenSSL, itu artinya program Anda belum statis sepenuhnya! Nah lalu gimana? Untuk membuat program statis penuh maka diperlukan semua dependensinya juga dikompilasi secara statis penuh, Anda harus terlebih dahulu mengkompilasi pustaka OpenSSL juga secara statis penuh, baru pustaka tersebut digunakan untuk build program Rust-nya. Duh kok ribet ya? Belum, lebih ribet lagi ketika Anda mencoba mengkompilasi dengan dependensi yang mana dependensi tersebut punya dependensi (dependencyception :D) dan kebetualan dependensinya dependensi itu bukan statis, contoh PostgreSQL punya dependensi openssl, ldap, dll, nah Anda harus kompilasi tuh satu-satu dependensinya ke statis!

Docker Sebagai Penyelamat

Kalau tadi Musl sebagai penyelamat untuk jawaban akan pustaka statis, Docker adalah penyelamat dari dependency hell kompilasi statis. Saya telah membuatkan docker images untuk hal ini: Docker musl build, dengan menggunakan image docker tersebut Anda tidak perlu repot-repot menginstall satu persatu toolchain, pustaka OpenSSL dan PostgreSQL statis, karena telah saya siapkan semua kebutuhannya tersebut, satu-satunya yang Anda butuhkan adalah Docker saja.

Dengan docker Anda bisa membuat semua program Rust Anda jadi statis penuh hanya dengan mengetikkan perintah:

$ docker run -it --rm \
     -v `pwd`:/workdir \
     anvie/rust-musl-build:latest \
     cargo build --release --target=x86_64-unknown-linux-musl

Perintah tersebut akan menghasilkan program statis penuh untuk mesin arsitektur x86_64 menggunakan Rust stable, apabila Anda ingin menggunakan Rust nightly gunakan tag nightly-*, contoh:

$ docker run -it --rm \
     -v `pwd`:/workdir \
     anvie/rust-musl-build:nightly-1.35.0 \
     cargo build --release --target=x86_64-unknown-linux-musl

Bagi "dockeriah" atau orang-orang ahli docker mungkin sadar bahwa perintah docker tersebut akan membuat pre-linked object yang dihasilkan ketika kompilasi sebelum di-link-kan menjadi satu kesatuan program utuh hilang ketika container-nya selesai build, ya karena object-nya akan dibuat di file-system dalam container, dan hal ini akan membuat setiap kompilasi jadi sangat lama, karena harus mengkompilasi semuanya lagi dari awal, namun tidak usah risau, Anda bisa mengakalinya dengan cara me-mapping beberapa direktori yang dibutuhkan oleh cargo ke dalam container menggunakan parameter -v:

$ docker run -it --rm \
     -v `pwd`:/workdir \
     -v ~/.cargo/git:/root/.cargo/git \
     -v ~/.cargo/registry:/root/.cargo/registry \
     anvie/rust-musl-build:latest \
     cargo build --release --target=x86_64-unknown-linux-musl

Dan hasil kompilasinya adalah statis penuh walaupun bergantung dengan pustaka OpenSSL dan PostgreSQL:

$ ldd target/x86_64-unknown-linux-musl/distoria
    not a dynamic executable

Dan, itu adalah output terindah yang pernah saya lihat dari sebuah perintah ldd :D

So satisfying musl linux ldd

[] Robin Sy.