Building Games in Rust with WebAssembly
Why Rust Fits WebAssembly So Well
Rust and WebAssembly grew up together, and the relationship shows in the tooling. Rust treats wasm32-unknown-unknown as a first-class compilation target, meaning the compiler can emit wasm directly with no separate toolchain to install the way C and C++ need Emscripten. Rust's design also happens to suit wasm: it has no garbage collector, so a Rust wasm module produces none of the collection pauses that can hitch a game, and its ownership system enforces the memory discipline that high-performance code needs anyway. You get the speed of manual memory management with the safety of a compiler that refuses to let you misuse it.
The other reason Rust is attractive for new wasm games is that it avoids a whole class of bugs that are especially nasty in the browser. Memory corruption in a C++ wasm module manifests as mysterious crashes or silent data corruption inside the sandbox, hard to debug through the JavaScript boundary. Rust's compile-time checks eliminate most of those before the program ever runs. For a solo developer or small team starting fresh, that safety net is worth a great deal, because debugging time is the scarcest resource and Rust spends a lot of it up front at compile time instead of at runtime in production.
The Core Tool: wasm-bindgen
Raw WebAssembly can only pass numbers across the boundary to JavaScript, which would make working with strings, objects, and browser APIs miserable. The tool that solves this is wasm-bindgen. It sits between your Rust code and JavaScript and generates the glue that lets the two call each other with real types: you can return a string from Rust and receive a string in JavaScript, call a browser function from Rust, or pass a structured value across, and wasm-bindgen handles the encoding and decoding underneath. Paired with it, the web-sys and js-sys crates expose the browser's own APIs to Rust, so you can touch the canvas, the document, or WebGL from Rust code through generated bindings.
For game work, you usually do not call these low-level bindings by hand, because a game engine wraps them. But understanding that wasm-bindgen is doing the heavy lifting explains how a Rust game reaches the browser at all, and it is what you reach for directly when you want a small, hand-built Rust core that JavaScript drives, the Rust equivalent of the lean hybrid approach described in the loading article. The trans-boundary ergonomics that wasm-bindgen provides are a big part of why Rust feels pleasant on the web where raw wasm does not.
Rust compiles to wasm with no garbage collector and no separate toolchain, and wasm-bindgen turns the number-only boundary into one that passes real types, which is why Rust is the popular choice for new wasm games.
The Quick Path: macroquad
For most people starting a Rust web game, the fastest route is macroquad, a simple, friendly game library designed to be cross-platform from the start. Macroquad handles the window, input, audio, and 2D drawing with a small, approachable API, and crucially it exports to WebAssembly with almost no extra effort. You write your game once, build it for the web target, and pair the resulting wasm with a small HTML and JavaScript loader that macroquad provides. The same code that runs on your desktop runs in the browser, which makes the develop-and-test loop fast because you can iterate natively and ship to the web at the end.
Macroquad is the right choice for 2D games, prototypes, game jams, and anyone who wants to feel the Rust-to-web pipeline working without wrestling a large engine. It deliberately keeps its surface small, so you are not learning a sprawling framework before you can draw a sprite. For a developer who knows some Rust and wants a playable browser game quickly, it is hard to beat, and it is a gentler on-ramp than the heavier engines below.
The Ambitious Path: Bevy
When the project is bigger, Bevy is the leading full Rust game engine, and it builds on an entity component system at its core, the same architectural pattern covered in the ECS article. Bevy targets WebAssembly as one of its platforms, so a Bevy game can run in the browser, with the usual tradeoff that the build is larger because you are shipping a real engine. Bevy supports both 2D and 3D, has a growing ecosystem of plugins, and uses Rust's type system heavily to make the ECS ergonomic and safe.
Bevy on the web is genuinely capable but carries the same considerations as any full-engine wasm export: download size matters, load time matters, and you should test on mobile because a heavy 3D Bevy game asks a lot of a phone browser. It is the right choice when you are committed to Rust for a substantial game and want a structured engine rather than a minimal library. For a first wasm game or a small one, macroquad gets you there with far less weight, and you can graduate to Bevy when the project's scope justifies it.
One Codebase, Native and Web
A quietly powerful benefit of building a game in Rust is that the same code targets the desktop and the browser at once. Because macroquad and Bevy are cross-platform, you develop and debug natively, where the iteration loop is fast and the debugger is full-featured, then build for the web target when you want a browser version. You are not maintaining two separate games. The native build is your development environment and a potential desktop release, and the wasm build is your web release, both from one source tree.
This matters for debugging especially, because debugging wasm through the browser is harder than debugging native code. When a bug appears, you can usually reproduce it in the native build, fix it with proper tools, and trust that the fix carries to the web, since it is the same logic compiled to a different target. The cases that only show up in the wasm build tend to be browser-specific, touching the canvas, input timing, or audio autoplay rules, which narrows the search considerably. Being able to do the bulk of your debugging natively is one of the practical reasons a single Rust codebase is pleasant to work on compared to writing browser-only code.
The Realistic Workflow
A typical Rust wasm game build uses a small set of well-worn tools. You add the wasm32 target to your Rust installation, then build with a helper like wasm-pack or trunk that runs the compile, invokes wasm-bindgen, and assembles the JavaScript and HTML around your wasm output. Trunk in particular is popular for web games because it acts like a bundler for Rust wasm projects, watching your files, rebuilding on change, and serving a live page, which gives you a develop loop closer to what web developers expect. The engine you chose, macroquad or Bevy, slots into this flow and handles the browser-facing details.
The honest caveat is the same one that applies to all wasm: the build is larger and the iteration loop is slower than pure JavaScript, because there is a compile step and a binary to load. Rust's compile times in particular are not instant. The payoff is performance, safety, and a single codebase that runs natively and on the web. If you are weighing Rust against starting in JavaScript, the decision article applies directly: choose Rust and wasm when you want native-class performance and safety from the start or already know Rust, and stay in JavaScript when the game is light and you want the fastest possible iteration and load.