Skip to content

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.

QuestionRustC / EmscriptenAssemblyScriptTinyGo
Team already writes this language?MaybeMaybeIf you know TSIf you know Go
Existing codebase to port?NoYesNoNo
Binary size priority?ExcellentLarge (libc overhead)ExcellentGood
GC / runtime overhead?NoneLibc onlyNoneMinimal
Rich JS interop (strings, objects)?Yes (wasm-bindgen)Via ccall/cwrapVia loaderLimited
Standard library coverage?Full stdFull libcLimitedPartial
Async / concurrency support?Via futuresVia AsyncifyLimitedLimited
Learning curve (Wasm-specific)?MediumLow (if you know C)Very low (TS)Low (if you know Go)

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.
Terminal window
wasm-pack build --target web
# → pkg/ ready to npm publish

C / 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.
Terminal window
emcc existing_lib.c -o out.js -sEXPORTED_FUNCTIONS='["_my_api"]' -O2

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.
Terminal window
npx asc assembly/index.ts --outFile build/release.wasm --optimize

Choose TinyGo when your team writes Go and you want to share pure-logic packages between a Go backend and a browser frontend:

Terminal window
tinygo build -o wasm.wasm -target wasm ./main.go

Note: 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.

  1. Have C/C++ code that already works? → Emscripten.
  2. Team writes TypeScript and wants a quick win? → AssemblyScript.
  3. Team writes Go? → TinyGo.
  4. Writing new code and want the best binary size + JS interop? → Rust.
OptionBenefitCost
Rust + wasm-bindgenMemory safety from the ownership model, very small binaries, no GCSteeper learning curve than the others — you need to understand ownership/borrowing
C/C++ + EmscriptenHuge C/C++ ecosystem, port an existing codebase almost immediatelyGlue code and the POSIX emulation layer add runtime overhead, producing a larger binary
AssemblyScriptFamiliar syntax for a TypeScript team, fastest to get startedMuch smaller ecosystem, and not semantically identical to TypeScript — the type system differs
  • 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 -O3 or -sMINIMAL_RUNTIME — so the bundle bloats
  • Treating AssemblyScript as if it were all of TypeScript, then using syntax or semantics asc doesn’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.

Which toolchain is the best choice when porting an existing C library that uses printf and malloc?
Which two toolchains produce the smallest Wasm binaries because they have no garbage collector or runtime heap?
What is the main limitation of TinyGo when compiling Go code to Wasm?
Which command publishes a Rust Wasm project as an npm package?