Rust dan WebAssembly
WebAssembly (sering disingkat Wasm) memungkinkan kamu menjalankan kode yang ditulis dalam bahasa seperti Rust di dalam browser, berdampingan dengan JavaScript. Bayangkan Wasm sebagai mesin kedua di browser: JavaScript tetap mengendalikan halaman, tapi kalau ada pekerjaan berat (filter gambar, simulasi fisika, membaca file besar) kamu bisa menyerahkannya ke kode Rust yang sudah di-compile dan cepat.
Di halaman ini kamu akan menulis library Rust kecil, meng-compile-nya ke Wasm, lalu memanggilnya dari halaman web. Tidak ada yang bisa dijalankan di Rust Playground di sini (butuh browser dan tool tambahan), jadi sebagian besar contoh berupa blok kode biasa untuk dijalankan di komputermu sendiri. Tapi bagian yang murni Rust bisa langsung kamu coba.
Apa itu WebAssembly, dan kenapa Rust cocok
Section titled “Apa itu WebAssembly, dan kenapa Rust cocok”WebAssembly adalah format biner yang ringkas untuk program. Semua browser utama bisa menjalankannya, dengan kecepatan mendekati kode native, di dalam sandbox keamanan yang sama dengan JavaScript. Wasm juga tidak hanya untuk browser: server dan platform edge pun menjalankan Wasm.
Rust adalah salah satu bahasa paling populer untuk Wasm, karena beberapa alasan:
- Tanpa garbage collector atau runtime besar. Rust tidak perlu membawa mesin tambahan bersama kodemu, jadi file
.wasmbisa tetap kecil. - Tool yang bagus.
wasm-bindgenmenuliskan kode perekat (glue code) antara Rust dan JavaScript untukmu, danwasm-packmembangun semuanya dengan satu perintah. - Aman dan cepat. Kamu mendapat keamanan memori dan performa yang sama seperti di desktop.
Siapkan tool-nya
Section titled “Siapkan tool-nya”Pertama, beri tahu Rust bahwa ia boleh meng-compile untuk target Wasm. Target artinya “jenis mesin tempat kode akan berjalan”; wasm32-unknown-unknown berarti “Wasm 32-bit, tanpa sistem operasi tertentu”:
rustup target add wasm32-unknown-unknownBerikutnya, instal wasm-pack. Tool ini meng-compile crate kamu, menjalankan wasm-bindgen, dan mengumpulkan semua yang kamu butuhkan ke dalam satu folder. Kamu bisa memakai installer resminya, atau membangunnya dengan Cargo:
# Installer Linux / macOS dari situs wasm-packcurl https://wasm-bindgen.github.io/wasm-pack/installer/init.sh -sSf | sh
# atau, di sistem apa pun yang sudah terpasang Rustcargo install wasm-pack --lockedTulis library Rust untuk Wasm
Section titled “Tulis library Rust untuk Wasm”Modul Wasm adalah sebuah library, bukan program dengan main. Buat satu:
cargo new --lib hello-wasmcd hello-wasmLalu edit Cargo.toml. Baris crate-type memberi tahu Cargo untuk menghasilkan library yang cocok untuk .wasm (cdylib), dan wasm-bindgen adalah crate (library Rust) yang menghubungkan Rust dengan JavaScript:
[package]name = "hello-wasm"version = "0.1.0"edition = "2021"
[lib]crate-type = ["cdylib", "rlib"]
[dependencies]wasm-bindgen = "0.2"Sekarang tulis dua fungsi di src/lib.rs. Atribut #[wasm_bindgen] menandai masing-masing sebagai “bisa dipanggil dari JavaScript”:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]pub fn add(a: i32, b: i32) -> i32 { a + b}
#[wasm_bindgen]pub fn greet(name: &str) -> String { format!("Halo, {name}! String ini dibuat di Rust.")}Hanya itu kode Rust yang kamu perlukan. Perhatikan bahwa keduanya fungsi biasa: wasm-bindgen yang mengurus perubahan string JavaScript menjadi &str, dan String yang dikembalikan menjadi string JavaScript lagi.
Uji logikanya secara native dulu
Section titled “Uji logikanya secara native dulu”Ini kebiasaan yang sangat menghemat waktu: taruh pekerjaan utamanya di fungsi Rust biasa, lalu uji di komputermu dengan cargo test atau main sederhana. Debugging di browser lebih lambat daripada di terminal. Logika yang sama, tanpa atribut, bisa berjalan di mana saja:
fn add(a: i32, b: i32) -> i32 { a + b}
fn greet(name: &str) -> String { format!("Halo, {name}! String ini dibuat di Rust.")}
fn main() { println!("{}", greet("Ferris")); println!("2 + 3 = {}", add(2, 3));}Output:
Halo, Ferris! String ini dibuat di Rust.2 + 3 = 5Setelah logikanya benar, pembungkus #[wasm_bindgen] hanyalah pintu tipis menuju browser. (Test yang ditandai #[test] juga berjalan secara native dengan cargo test; lihat Testing.)
Build dengan wasm-pack
Section titled “Build dengan wasm-pack”Dari folder crate, jalankan:
wasm-pack build --target web--target web artinya “buat file yang bisa dimuat langsung oleh browser lewat tag <script type="module">”, tanpa perlu bundler seperti webpack. Saat kami menjalankannya, folder pkg/ yang baru berisi:
pkg/├── hello_wasm_bg.wasm kode Rust yang sudah di-compile├── hello_wasm.js glue code JavaScript yang memuatnya dan membungkus tiap fungsi├── hello_wasm.d.ts tipe TypeScript untuk fungsi-fungsimu├── hello_wasm_bg.wasm.d.ts└── package.jsonTanda hubung di hello-wasm berubah menjadi garis bawah di nama file. Secara default wasm-pack build membuat build release (yang dioptimalkan); tambahkan --dev untuk build yang lebih cepat selesai tapi tidak dioptimalkan selama kamu bereksperimen.
Panggil dari JavaScript
Section titled “Panggil dari JavaScript”Buat index.html di samping Cargo.toml (supaya pkg/ berada di sebelahnya):
<!doctype html><html lang="id"><head> <meta charset="utf-8"> <title>Halo dari Rust</title></head><body> <p id="message">Memuat…</p>
<script type="module"> import init, { add, greet } from "./pkg/hello_wasm.js";
await init(); // unduh dan jalankan file .wasm
const text = greet("Ferris"); const sum = add(2, 3); document.getElementById("message").textContent = `${text} 2 + 3 = ${sum}`; </script></body></html>Export default-nya, init(), mengunduh file .wasm lalu menjalankannya. Setelah itu, add dan greet menjadi fungsi JavaScript biasa.
Browser tidak mau memuat modul dari alamat file://, jadi kamu butuh web server lokal kecil. Kalau Python sudah terpasang, ini cara tercepat:
python3 -m http.server 8080Buka http://localhost:8080 di browser. Saat kami mencobanya, halaman menampilkan:
Halo, Ferris! String ini dibuat di Rust. 2 + 3 = 5Kalimat itu dibuat oleh Rust, yang berjalan di dalam browser. 🦀
Apa yang murah melewati batas (dan apa yang tidak)
Section titled “Apa yang murah melewati batas (dan apa yang tidak)”Rust dan JavaScript tidak berbagi memori dengan cara yang sama. Modul Wasm punya blok memorinya sendiri, dan nilai harus berpindah antara kedua sisi. Sebagian perpindahan gratis; sebagian lagi butuh salinan:
| Tipe di Rust | Di JavaScript | Biaya |
|---|---|---|
i32, u32, f64, bool |
number, boolean |
Murah: dikirim langsung |
&str, String |
string |
Disalin, ditambah konversi UTF-16 ke UTF-8 (atau sebaliknya) |
&[u8], Vec<u8> |
Uint8Array |
Disalin ke (atau dari) memori Wasm |
&mut [u8] |
Uint8Array |
Disalin masuk, lalu disalin balik setelah pemanggilan |
Jadi aturan emasnya: buat sedikit pemanggilan, tapi masing-masing mengerjakan banyak hal. Memanggil Rust sekali untuk memproses satu gambar penuh itu cepat. Memanggil Rust sejuta kali, sekali per piksel, akan menghabiskan sebagian besar waktunya untuk menyeberangi batas itu.
Seperti ini contohnya. Fungsi berikut mencerahkan seluruh buffer nilai piksel dalam satu pemanggilan:
use wasm_bindgen::prelude::*;
/// Buat setiap piksel lebih terang. JS mengirim Uint8Array.#[wasm_bindgen]pub fn brighten(pixels: &mut [u8], amount: u8) { for p in pixels.iter_mut() { *p = p.saturating_add(amount); }}Dari JavaScript, setelah await init(), kamu memanggil brighten(pixels, 20) dengan sebuah Uint8Array, dan array itu diperbarui langsung di tempat. Perulangannya sendiri murni Rust, jadi kamu bisa mengujinya secara native:
fn brighten(pixels: &mut [u8], amount: u8) { for p in pixels.iter_mut() { // saturating_add berhenti di 255, bukan overflow *p = p.saturating_add(amount); }}
fn main() { let mut pixels = [10, 100, 250]; brighten(&mut pixels, 20); println!("{pixels:?}");}Output:
[30, 120, 255]Perhatikan bahwa 250 + 20 menjadi 255, bukan crash atau kembali ke nol: saturating_add berhenti di nilai maksimum. Itu persis yang kamu mau untuk warna.
Jaga agar file .wasm tetap kecil
Section titled “Jaga agar file .wasm tetap kecil”Setiap byte dari file .wasm harus diunduh dulu sebelum halaman bisa memakainya. Beberapa tips:
- Selalu kirim build release. Untuk contoh kecil kita (versi dengan teks bahasa Inggris),
wasm-pack build --devmenghasilkan file.wasmsebesar 58.591 byte; build release default hanya 21.530 byte. (Itu hasil di mesin kami denganwasm-optdimatikan; angkamu bisa berbeda.) - Biarkan wasm-pack menjalankan
wasm-opt. Dalam mode release, wasm-pack secara default menjalankan optimizerwasm-optdari Binaryen pada hasilnya, yang biasanya membuatnya lebih kecil lagi. - Aktifkan kompresi di server. File kami yang 21.530 byte menjadi 8.464 byte setelah gzip.
- Coba pengaturan yang fokus pada ukuran, lalu ukur:
[profile.release]opt-level = "z" # optimalkan ukuran ("s" versi yang lebih ringan)lto = truecodegen-units = 1Jujurlah pada dirimu sendiri dan ukur hasilnya. Untuk contoh dua fungsi kita, pengaturan ini tidak membantu: filenya jadi 22.817 byte, sedikit lebih besar. Pengaturan ini biasanya baru terasa manfaatnya di crate yang lebih besar. Hal seperti format! dan pesan panic juga ikut menarik kode, jadi crate yang menghindarinya bisa jauh lebih kecil.
Di mana Wasm dipakai di dunia nyata
Section titled “Di mana Wasm dipakai di dunia nyata”Beberapa contoh, masing-masing dengan tulisan dari perusahaannya sendiri (bahasa Inggris):
- Figma meng-compile editor desainnya yang ditulis dalam C++ ke WebAssembly dan melaporkan waktu muatnya turun 3x. (Bukan Rust, tapi ini menunjukkan apa yang bisa dilakukan Wasm di aplikasi besar.)
- Cloudflare Workers mendukung Rust lewat crate
workers-rs: kode Rust kamu di-compile ke Wasm dan berjalan di server Cloudflare di seluruh dunia. - Shopify Functions adalah modul WebAssembly untuk mengustomisasi toko, dan Shopify merekomendasikan Rust sebagai bahasa dengan performa terbaik untuk itu.
- Ekstensi browser 1Password memindahkan fitur pengisian halamannya ke WebAssembly dan melaporkan kecepatannya minimal dua kali lipat.
Kesalahan umum
Section titled “Kesalahan umum”Lupa crate-type = ["cdylib"]. Tanpa baris ini, wasm-pack langsung berhenti. Untungnya, pesan error-nya memberi tahu persis apa yang harus ditambahkan:
Error: crate-type must be cdylib to compile to WebAssembly. Add the following to your Cargo.toml file:
[lib]crate-type = ["cdylib", "rlib"]Membuka index.html dengan klik dua kali. Halaman tetap menampilkan “Memuat…”, dan console browser (di Chrome) menulis:
Access to script at 'file:///…/pkg/hello_wasm.js' from origin 'null' has been blocked by CORS policy: Cross origin requests are only supported for protocol schemes: chrome, chrome-extension, chrome-untrusted, data, http, https, isolated-app.Artinya, browser memblokir script yang dimuat dari file://. Solusinya: sajikan folder itu dengan web server lokal, misalnya python3 -m http.server, lalu buka alamat http://localhost.
Memanggil fungsi sebelum await init(). Modul Wasm belum dimuat, jadi glue code tidak punya apa pun untuk dipanggil. Saat kami menghapus baris init(), Chrome menampilkan Cannot read properties of undefined (reading '__wbindgen_free'), yaitu ada objek yang masih kosong (undefined). Selalu await init() dulu.
Langkah berikutnya: aplikasi web penuh dengan Rust
Section titled “Langkah berikutnya: aplikasi web penuh dengan Rust”Memanggil beberapa fungsi Rust dari JavaScript adalah awal yang bagus. Kalau kamu ingin membangun seluruh aplikasi web dengan Rust, lihat framework berikut. Keduanya di-compile ke Wasm dan memungkinkan kamu menulis komponen dalam Rust, mirip React:
- Leptos: framework modern yang cepat dan juga bisa me-render halaman di server.
- Yew: framework berbasis komponen yang sudah matang.
Ringkasan
Section titled “Ringkasan”- WebAssembly adalah format biner yang cepat dan aman yang bisa dijalankan browser (dan server); Rust cocok di-compile ke sana karena tidak punya garbage collector.
- Tambahkan target dengan
rustup target add wasm32-unknown-unknown, lalu instalwasm-pack. - Tandai fungsi dengan
#[wasm_bindgen], aturcrate-type = ["cdylib", "rlib"], dan build denganwasm-pack build --target web. - Di JavaScript,
await init()dulu, baru panggil fungsimu; sajikan halaman lewathttp://, bukanfile://. - Angka murah melewati batas; string dan slice disalin. Jadi buat sedikit pemanggilan yang masing-masing mengerjakan banyak hal.
- Kirim build release dengan kompresi, dan ukur dulu sebelum memercayai trik pengecilan ukuran.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Perintah mana yang menambahkan target Rust untuk WebAssembly di browser?
2. Apa fungsi atribut #[wasm_bindgen]?
3. Halamanmu tetap menampilkan "Memuat…" dan console menunjukkan error CORS untuk alamat file:///. Apa solusinya?
4. Mana yang paling murah dikirim dari JavaScript ke Rust?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Compiling from Rust to WebAssembly — MDN (bahasa Inggris): tutorial langkah demi langkah untuk membangun dan menjalankan proyek wasm-pack.
- The wasm-bindgen Guide (bahasa Inggris): semua tipe yang didukung, cara memanggil API browser, dan banyak contoh.
- Dokumentasi wasm-pack (bahasa Inggris): semua opsi
build, target, dan profile. - wasm32-unknown-unknown — The rustc book (bahasa Inggris): apa saja yang didukung target ini dan batasannya.
- WebAssembly di MDN (bahasa Inggris): apa itu Wasm dan bagaimana browser menjalankannya, terlepas dari Rust.