A WebAssembly (röviden Wasm) mára stabil, minden modern böngészőben támogatott technológia, és a Rust ökoszisztéma kifejezetten jól felkészült arra, hogy ebbe a világba exportáljuk a kódunkat. Ma megnézzük, hogyan indíthatsz el egy Rust-projektet, ami a böngészőben fut, és hogyan kommunikál a JavaScripttel.
Miért érdemes Rust-ot használni frontend logikához
A JavaScript kiváló nyelv a DOM manipulálására és az események kezelésére, de amikor számításigényes feladatokról van szó – képfeldolgozás, fizikai szimuláció, kriptográfia, parsing – bizony megfájdulhat a fejünk a teljesítménytől. Itt jön képbe a WebAssembly: bináris formátum, amit a böngésző motorja majdnem natív sebességgel tud futtatni.
A Rust ehhez azért ideális, mert:
- Nincs garbage collector, tehát nincs GC-szünet, ami megakaszthatja az animációt vagy a real-time feldolgozást.
- Az ownership rendszer memóriabiztosságot ad futásidejű költség nélkül – ez különösen fontos, ha a Wasm modulodnak nagy adatstruktúrákkal kell dolgoznia.
- A típusrendszer és a
Result/Optionkombináció sokkal megbízhatóbb hibakezelést biztosít, mint a JavaScript kivételalapú megközelítése.
Nem kell az egész frontendet Rust-ra átírnod! A leggyakoribb és leghasznosabb minta az, hogy csak a számításigényes „hot path”-eket írod meg Rust-ban, a UI-t és az eseménykezelést pedig JavaScriptben (vagy TypeScriptben) hagyod.
wasm-pack telepítése és projekt inicializálása
A wasm-pack a legegyszerűbb módja annak, hogy Rust crate-ből böngészőbarát Wasm-csomagot építs. Telepítsd a következő paranccsal (feltéve, hogy már van Rust toolchain-ed, jelen esetben stabil 1.68-as):
cargo install wasm-pack
Ezután hozz létre egy új könyvtár típusú crate-et:
cargo new --lib hello-wasm
cd hello-wasm
A Cargo.toml-ban jelöld ki, hogy a crate típusa cdylib legyen, és add hozzá a wasm-bindgen függőséget:
[package]
name = "hello-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
wasm-bindgen = "0.2"
A cdylib crate-típus azt jelenti, hogy a fordító egy dinamikus könyvtárat épít, amit a WebAssembly runtime tud betölteni. Az rlib hozzáadása azért hasznos, hogy a crate-et natív Rust tesztekben is tudd használni cargo test-tel.
wasm-bindgen attribútumok: JavaScript és Rust közötti kommunikáció
A wasm-bindgen a híd a Rust és a JavaScript világ között. A #[wasm_bindgen] attribútummal jelölt függvények, struct-ok és enum-ok automatikusan exportálódnak úgy, hogy JavaScriptből hívhatók legyenek.
Írjunk egy egyszerű üdvözlő függvényt:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
pub fn greet(name: &str) -> String {
format!("Szia, {}! Ez itt Rust kód a böngészőben.", name)
}
Ha a JavaScript oldalon szeretnél Rust felől hívható konzol logolást, ahhoz importálnod kell a böngésző API-ját. Ehhez az extern blokk és a #[wasm_bindgen] kombinációja szükséges:
use wasm_bindgen::prelude::*;
#[wasm_bindgen]
extern "C" {
#[wasm_bindgen(js_namespace = console)]
fn log(s: &str);
}
#[wasm_bindgen]
pub fn log_message(msg: &str) {
log(&format!("[Rust] {}", msg));
}
Ez lényegében deklarálja, hogy a console.log JavaScript funkció elérhető Rust oldalról is – innen már bármikor tudsz debug logolni a Rust kódodból.
A wasm-bindgen verziója a Cargo.toml-ban és a CLI eszköz verziója (amit a wasm-pack húz be) mindig legyen kompatibilis egymással. Ha eltérés van, gyakran kriptikus fordítási vagy futásidejű hibát kapsz. Érdemes lefixálni a pontos verziót a Cargo.toml-ban.
Egyszerű interaktív komponens megírása és futtatása böngészőben
Nézzünk egy kicsit komplexebb példát: egy számlálót, amit egy gombra kattintva növelünk, és a Rust oldalon tartjuk az állapotot egy struct-ban.
use wasm_bindgen::prelude::*;
use std::cell::RefCell;
#[wasm_bindgen]
pub struct Counter {
value: RefCell<i32>,
}
#[wasm_bindgen]
impl Counter {
#[wasm_bindgen(constructor)]
pub fn new() -> Counter {
Counter {
value: RefCell::new(0),
}
}
pub fn increment(&self) -> i32 {
let mut v = self.value.borrow_mut();
*v += 1;
*v
}
pub fn get(&self) -> i32 {
*self.value.borrow()
}
}
Fontos, hogy a #[wasm_bindgen] által exportált struct-ok mezői nem lehetnek publikusak automatikusan hozzáférhetők JS oldalról – kizárólag a metódusokon keresztül kommunikálunk, ami egyébként jól illik a Rust encapsulation-filozófiájához.
JavaScript oldalon (moduláris importtal) így néz ki a használat, miután lefordítottuk a csomagot:
import init, { Counter } from "./pkg/hello_wasm.js";
async function run() {
await init();
const counter = new Counter();
document.getElementById("btn").addEventListener("click", () => {
const value = counter.increment();
document.getElementById("output").textContent = value;
});
}
run();
Az init() hívás tölti be a .wasm bináris fájlt aszinkron módon – ezt mindig meg kell várni (await), mielőtt a modul funkcióit használnánk.
Ne felejtsd el, hogy a Wasm modul betöltése aszinkron folyamat! Ha közvetlenül szinkron módon próbálod meg hívni a Rust funkciókat az init() promise feloldása előtt, undefined hibát vagy exception-t kapsz.
Build és csomagolás npm/webpack integrációhoz
A wasm-pack build parancs generálja a böngészőben (vagy Node.js-ben) használható csomagot. A leggyakoribb target-ek:
# Böngészőbarát ES modul, natívan importálható <script type="module">-ban
wasm-pack build --target web
# Bundler (webpack, rollup) számára optimalizált kimenet
wasm-pack build --target bundler
# Node.js CommonJS környezethez
wasm-pack build --target nodejs
A build eredménye a pkg/ mappában landol, benne a .wasm bináris, egy generált .js wrapper, TypeScript deklarációs fájl (.d.ts), és egy package.json, amit akár közvetlenül npm publish-elhetsz is.
Ha webpack projektbe szeretnéd integrálni, a package.json-ban egyszerűen hivatkozhatsz a lokális pkg mappára:
{
"dependencies": {
"hello-wasm": "file:../hello-wasm/pkg"
}
}
Ezután a webpack konfigurációban engedélyezned kell a WebAssembly-modulok kezelését – ha webpack 5-öt használsz, ez alapból beépített funkcionalitás (experiments.asyncWebAssembly).
Ha CI/CD pipeline-ban dolgozol, érdemes a wasm-pack build --release opciót használni, mert alapértelmezésben a debug build sokkal nagyobb és lassabb bináris fájlt eredményez.
Teljesítménybeli szempontok és hibakeresési tippek
A WebAssembly gyors, de van néhány buktató, amire figyelni kell:
Bináris méret. A Rust standard library egy része (pl. panic infrastruktúra, formázás) jelentős méretet ad a végleges .wasm fájlhoz. Csökkentheted ezt a következőkkel:
[profile.release]
opt-level = "z" # méret-orientált optimalizáció
lto = true
codegen-units = 1
panic = "abort" # elhagyja a panic unwind kódot
Adatátvitel a JS és Wasm között. A wasm-bindgen alapból stringeket és egyszerű típusokat automatikusan konvertál, de ez másolással jár. Ha nagy mennyiségű bináris adatot (pl. képpontokat) kell átadni, érdemes &[u8] szeletet vagy Vec<u8>-t használni, és kerülni a felesleges JSON szerializálást.
Debugolás. A console_error_panic_hook crate-tel a Rust panic-ok olvasható stack trace-ként jelennek meg a böngésző konzolján, panic helyett a kriptikus „unreachable executed” hibaüzenet helyett:
use wasm_bindgen::prelude::*;
#[wasm_bindgen(start)]
pub fn main() {
console_error_panic_hook::set_once();
}
A #[wasm_bindgen(start)] attribútummal jelölt függvény automatikusan lefut a modul betöltésekor – kiváló hely az inicializáláshoz.
A let-else szintaxis (ami már stabil ebben a Rust verzióban) nagyon jól jön hibakezelő kódrészletekben, amikor egy Option vagy Result értéket akarsz kicsomagolni, és korai visszatérést akarsz, ha az sikertelen:
fn parse_input(s: &str) -> Option<i32> {
let Ok(value) = s.parse::<i32>() else {
return None;
};
Some(value * 2)
}
Összefoglalás
A Rust és a WebAssembly kombinációja izgalmas lehetőségeket nyit meg a frontend fejlesztésben: natív sebesség, típusbiztonság, és egy kiforrott eszközlánc (wasm-pack, wasm-bindgen), amivel viszonylag kevés fájdalommal illeszthetjük be a meglévő JavaScript/npm workflow-nkba. Nem kell az egész appot átírni – elég a számításigényes részeket kiszervezni Rust-ba, a maradékot pedig hagyni a JavaScript kényelmén. Ha még nem próbáltad, mostantól semmi nem akadályoz meg, hogy elindulj: telepítsd a wasm-pack-et, hozz létre egy cdylib crate-et, és nézd meg, mennyire egyszerű a Rust kódodat a böngészőbe csempészni.