Choosing a Language
Every Wasm toolchain reaches the same finish line — a .wasm binary that runs in the browser — but each follows a different road. The right choice depends on your existing codebase, your team’s skills, and the constraints of your project.
Decision table
Section titled “Decision table”| Question | Rust | C / Emscripten | AssemblyScript | TinyGo |
|---|---|---|---|---|
| Team already writes this language? | Maybe | Maybe | If you know TS | If you know Go |
| Existing codebase to port? | No | Yes | No | No |
| Binary size priority? | Excellent | Large (libc overhead) | Excellent | Good |
| GC / runtime overhead? | None | Libc only | None | Minimal |
| Rich JS interop (strings, objects)? | Yes (wasm-bindgen) | Via ccall/cwrap | Via loader | Limited |
| Standard library coverage? | Full std | Full libc | Limited | Partial |
| Async / concurrency support? | Via futures | Via Asyncify | Limited | Limited |
| Learning curve (Wasm-specific)? | Medium | Low (if you know C) | Very low (TS) | Low (if you know Go) |
Rust — when to choose it
Section titled “Rust — when to choose it”Choose Rust when you are writing new Wasm-first code and want:
- The smallest possible binary (no GC, no runtime heap).
- Automatic, type-safe JS bindings via
wasm-bindgen. - A crate ecosystem designed for
wasm32-unknown-unknown. - Publishable npm packages from
wasm-pack.
wasm-pack build --target web# → pkg/ ready to npm publishC / C++ with Emscripten — when to choose it
Section titled “C / C++ with Emscripten — when to choose it”Choose Emscripten when you are porting an existing C/C++ library and need:
printf,malloc, file I/O, and POSIX APIs to work without changes.- OpenGL emulation via WebGL (
-sUSE_WEBGL2). - A single-step build from a standard Makefile or CMake project.
emcc existing_lib.c -o out.js -sEXPORTED_FUNCTIONS='["_my_api"]' -O2AssemblyScript — when to choose it
Section titled “AssemblyScript — when to choose it”Choose AssemblyScript when your team writes TypeScript and you need:
- A very gentle learning curve (same syntax, different numeric types).
- A tiny, self-contained Wasm module with no dependencies.
- Fast iteration without setting up a Rust or C toolchain.
npx asc assembly/index.ts --outFile build/release.wasm --optimizeTinyGo — when to choose it
Section titled “TinyGo — when to choose it”Choose TinyGo when your team writes Go and you want to share pure-logic packages between a Go backend and a browser frontend:
tinygo build -o wasm.wasm -target wasm ./main.goNote: TinyGo does not support the full Go standard library. Packages that use net, os, or reflection may not compile. Check the TinyGo package support table before committing.
Quick-pick flowchart
Section titled “Quick-pick flowchart”- Have C/C++ code that already works? → Emscripten.
- Team writes TypeScript and wants a quick win? → AssemblyScript.
- Team writes Go? → TinyGo.
- Writing new code and want the best binary size + JS interop? → Rust.
Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Rust + wasm-bindgen | Memory safety from the ownership model, very small binaries, no GC | Steeper learning curve than the others — you need to understand ownership/borrowing |
| C/C++ + Emscripten | Huge C/C++ ecosystem, port an existing codebase almost immediately | Glue code and the POSIX emulation layer add runtime overhead, producing a larger binary |
| AssemblyScript | Familiar syntax for a TypeScript team, fastest to get started | Much smaller ecosystem, and not semantically identical to TypeScript — the type system differs |
Common mistakes
Section titled “Common mistakes”- Choosing a language purely by team familiarity without checking the resulting binary size — e.g. using Emscripten for a small module with no existing codebase to port, shipping a runtime far bigger than needed
- Shipping an unoptimized Emscripten build — not stripping the POSIX emulation layer and not using flags like
-O3or-sMINIMAL_RUNTIME— so the bundle bloats - Treating AssemblyScript as if it were all of TypeScript, then using syntax or semantics
ascdoesn’t support, hitting a compile error or a hard-to-track bug
💡 Real-world example
Figma chose C++ with Emscripten for the rendering engine it ported from desktop, measuring more than a 3x improvement in load time after the switch (Figma engineering blog). Shopify Functions, by contrast, lets merchants write custom checkout logic that compiles to Wasm — currently in Rust or JavaScript (Shopify Functions docs) — two examples that picked different languages for different requirements.