Pengenalan WebAssembly

Target wasm32 dan tur singkat wasm-bindgen.

Daftar isi

Topik ini membahas WebAssembly (wasm) sebagai target keluaran Rust, yaitu format biner portabel yang dijalankan oleh host seperti browser dan Node di dalam mesin virtual yang terisolasi. Pembahasan mencakup arti target wasm32-unknown-unknown, pembangunan crate Rust menjadi berkas .wasm melalui kompilasi silang, pemanggilan fungsi yang diekspor dari sisi host, serta peran wasm-bindgen sebagai jembatan tipe yang lazim dipakai pada proyek nyata. Setelah membaca topik ini, modul Rust dapat dibangun dan dijalankan sebagai berkas .wasm hanya dengan alat yang sudah tersedia pada pemasangan Rust standar.

Target wasm32 sebagai kompilasi silang#

Target kompilasi pada Rust dinyatakan dengan rangkap tiga (target triple) yang memuat arsitektur, vendor, dan sistem operasi, misalnya x86_64-unknown-linux-gnu untuk program Linux biasa. Target wasm32-unknown-unknown dibaca secara harfiah: arsitektur WebAssembly 32 bit, tanpa vendor, tanpa sistem operasi. Bagian unknown bukan tanda kegagalan, melainkan pernyataan bahwa kode hasil kompilasi tidak mengasumsikan lingkungan host tertentu, sehingga modulnya dapat berjalan di browser, di Node, maupun pada runtime WebAssembly yang berdiri sendiri.

rustc sejak awal dirancang sebagai kompilator silang, sehingga berpindah target tidak mengubah kode sumber sama sekali. Yang berubah adalah keluaran akhirnya: alih-alih binari sistem operasi, kompilator menghasilkan berkas .wasm yang memuat instruksi mesin virtual WebAssembly beserta bagian ekspor yang mendaftarkan fungsi yang dapat dipanggil host. Pustaka standar tetap tersedia pada target ini karena rustup mengirimkannya sebagai komponen per target, tetapi sebagian fungsinya hanya berupa fungsi kosong: API berkas, jaringan, dan thread tidak tersedia karena host browser memang tidak menyediakan layanan tersebut.

Keberadaan target pada pemasangan dapat diperiksa kapan saja. Perintah berikut memuat daftar target yang terpasang pada mesin ini, dan target wasm32 harus menjadi salah satu barisnya sebelum contoh pada bagian berikut dapat dibangun.

bash
# periksa target yang terpasang untuk rustc
rustup target list --installed
text Keluaran
wasm32-unknown-unknown
x86_64-unknown-linux-gnu

Membangun crate menjadi modul wasm#

Modul wasm tidak memiliki titik masuk main pada makna biasa, sebab eksekusinya dimulai ketika host memanggil fungsi yang diekspor. Bentuk crate yang sesuai adalah pustaka dengan crate-type = ["cdylib"], yakni pustaka yang dikompilasi agar dapat dimuat dari luar, bukan untuk diimpor oleh crate Rust lain. Fungsi yang ingin dipanggil host membutuhkan dua penanda: pub extern "C" agar keluar sebagai simbol dengan konvensi pemanggilan C, dan #[no_mangle] agar namanya tidak diubah kompilator. Pola ini serupa dengan pembahasan FFI karena host memang memanggil Rust melalui ABI C.

toml Cargo.toml
[package]
name = "kalkulator-wasm"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]
rust src/lib.rs
// tidak-dijalankan sendirian oleh rustc karena pustaka ini dibangun cargo untuk target wasm32.

/// Menjumlahkan dua bilangan bulat untuk dipanggil host WebAssembly.
#[no_mangle]
pub extern "C" fn tambah(a: i32, b: i32) -> i32 {
    a + b
}

Proyek kecil dirakit di luar pohon konten dengan menyalin kedua berkas dari bundle, lalu dibangun dengan opsi --target. Proses ini hanya melibatkan pustaka standar tanpa dependensi eksternal, sehingga tidak ada unduhan dan hasilnya muncul dalam waktu singkat.

bash
# siapkan proyek kecil yang bersih di luar pohon konten
rm -rf /tmp/proyek-wasm
mkdir -p /tmp/proyek-wasm/src
# salin berkas dari bundle ke posisinya masing-masing
cp Cargo.toml /tmp/proyek-wasm/Cargo.toml
cp src/lib.rs /tmp/proyek-wasm/src/lib.rs
# bangun untuk target wasm32 tanpa dependensi eksternal
cargo build --manifest-path /tmp/proyek-wasm/Cargo.toml --target wasm32-unknown-unknown -q
# tampilkan artefak yang dihasilkan
ls /tmp/proyek-wasm/target/wasm32-unknown-unknown/debug/*.wasm
text Keluaran
/tmp/proyek-wasm/target/wasm32-unknown-unknown/debug/kalkulator_wasm.wasm

Artefak kalkulator_wasm.wasm berada pada subdirektori target yang mengikuti nama target, terpisah dari hasil bangun bawaan agar berkas untuk kedua target tidak saling menimpa. Berkas ini siap dimuat oleh host mana pun. Program host berikut memuatnya di Node: isi berkas dibaca menjadi penyangga bita, dikompilasi menjadi modul, diinstansiasi, lalu fungsi tambah dipanggil seperti fungsi JavaScript biasa.

js panggil-wasm.js
const fs = require('node:fs');

// berkas modul dihasilkan oleh blok bangun sebelumnya
const berkas = '/tmp/proyek-wasm/target/wasm32-unknown-unknown/debug/kalkulator_wasm.wasm';

const modul = new WebAssembly.Module(fs.readFileSync(berkas));
const instansi = new WebAssembly.Instance(modul, {});

console.log('tambah(30, 12) =', instansi.exports.tambah(30, 12));
console.log('tambah(-5, 5)  =', instansi.exports.tambah(-5, 5));
text Keluaran
tambah(30, 12) = 42
tambah(-5, 5)  = 0

Tidak ada satu baris JavaScript pun yang menghitung 30 + 12, karena hasilnya benar-benar dihitung oleh instruksi Rust di dalam mesin WebAssembly milik Node. Alur berkas ini ringkas, sebagaimana dipetakan diagram berikut: satu crate Rust masuk, satu modul wasm keluar, dan host mana pun dapat menginstansiasinya.

src/lib.rscrate Rustkompilasi silangkalkulator_wasm.wasmmodul WebAssemblyinstansiasihostNode, browserhost mana pun dapat menginstansiasi modul dan memanggil fungsi yang diekspornya
Perjalanan modul dari kode Rust ke host

Tur singkat wasm-bindgen#

Ekspor pada contoh di atas hanya praktis untuk bilangan dan pointer karena ABI C memang hanya mengenal bentuk data sederhana itu. Tipe teks, array, dan objek milik host memerlukan pengemasan manual yang panjang dan rawan salah apabila ditulis tangan. Crate wasm-bindgen mengisi kekurangan tersebut: anotasi #[wasm_bindgen] pada fungsi menghasilkan perekat JavaScript yang menerjemahkan String, &str, closure, dan objek host secara otomatis, sehingga sisi JavaScript cukup memanggil fungsi biasa dengan argumen biasa.

rust Contoh: fungsi Rust dengan anotasi wasm_bindgen
// tidak-dijalankan karena wasm-bindgen adalah crate eksternal di luar lingkungan ini.
use wasm_bindgen::prelude::*;

#[wasm_bindgen]
pub fn sapa(nama: &str) -> String {
    format!("Halo, {nama}!")
}

Pada proyek nyata, wasm-bindgen hampir selalu dipasangkan dengan wasm-pack, alat yang merangkai langkah bangun, pemrosesan modul, dan pengemasan hasil untuk browser maupun bundler. Keduanya membutuhkan crate eksternal serta wasm-bindgen-cli, sehingga contoh di sini dibatasi pada bentuk kodenya saja. Introspeksi isi modul juga memerlukan alat khusus: wasm-tools print menampilkan berkas .wasm dalam format teks WAT beserta daftar ekspornya, yang berguna untuk memastikan fungsi benar-benar diekspor dengan nama yang diharapkan.

bash
# tidak-dijalankan karena wasm-tools belum terpasang di lingkungan ini.
wasm-tools print /tmp/proyek-wasm/target/wasm32-unknown-unknown/debug/kalkulator_wasm.wasm

Kesalahan umum#

Memanggil fungsi yang tidak diekspor. Tanpa pub extern "C" sekaligus #[no_mangle], fungsi tetap terkompilasi tetapi tidak muncul pada bagian ekspor modul, sehingga exports.tambah bernilai undefined di sisi host. Pasang kedua penanda pada setiap fungsi yang ingin dipanggil, lalu periksa hasilnya pada daftar ekspor.

Memakai crate yang menuntut sistem operasi. Crate yang membuka berkas atau menjalankan thread gagal dikompilasi untuk wasm32-unknown-unknown karena API tersebut tidak tersedia pada target. Periksa dukungan wasm pada dokumentasi crate sebelum menambahkannya sebagai dependensi, atau cari alternatif yang dirancang untuk lingkungan tanpa sistem operasi.

Mengirim String pada fungsi ekspor polos. ABI C hanya mengenal bilangan dan pointer, sehingga argumen teks harus diwakili pointer bita plus panjangnya, atau ditangani wasm-bindgen yang melakukan pengemasan tersebut. Kompilator memberi peringatan improper_ctypes_definitions karena String tidak aman bagi ABI C, dan peringatan itu sebaiknya tidak diabaikan.

Ringkasan#

  • Target wasm32-unknown-unknown menghasilkan modul .wasm tanpa asumsi host, dan pustaka standar hadir dengan sebagian API hanya berupa fungsi kosong.
  • Crate cdylib dengan #[no_mangle] dan pub extern "C" mengekspor fungsi yang dapat dipanggil host melalui ABI C.
  • Host memuat berkas, menginstansiasi modul, lalu memanggil fungsi ekspor, dan Node memadai sebagai host percobaan tanpa browser.
  • wasm-bindgen menghasilkan perekat JavaScript untuk tipe selain bilangan, dan wasm-pack merangkai seluruh alurnya pada proyek nyata.

Referensi#