Choosing a Rust Wasm Target Triple
This page answers one question: Rust offers several WebAssembly targets — which one should a given project build for, and what changes in the code and the output when you pick each?
Prerequisites
- [ ] Rust 1.81 or newer (
wasm32-wasip2andwasm32v1-noneare tier 2 targets from 1.82 onward). - [ ] A clear idea of where the module will run: a browser, a WASI runtime, an edge platform, or an embedded host.
- [ ]
rustup target list | grep wasmto see what is installed.
A target triple describes the host, not the instruction set
All Rust WebAssembly targets produce the same kind of bytecode. What differs is what the compiler and the standard
library assume about the environment that will run it — which functions it can import, how it gets memory, whether
there is a file system, a clock, environment variables, or a way to print. That is what the triple encodes: an
architecture (wasm32), a vendor, and an operating-system-like “environment” component.
wasm32-unknown-unknown assumes nothing. There is no operating system, so the standard library’s I/O, time,
threading and process functions compile but fail at runtime — they return errors or panic. Everything the module
needs from outside must be imported explicitly, which in browser projects means wasm-bindgen and web-sys.
wasm32-wasip1 assumes a host that implements WASI preview 1: a POSIX-flavoured set of imports for files,
clocks, random numbers, environment and arguments. std::fs, std::env and println! work, because the standard
library routes them to those imports. wasm32-wasip2 assumes a WASI preview 2 host and produces a component
rather than a core module, using the component model’s typed interfaces.
wasm32v1-none assumes even less than unknown-unknown: no standard library at all (no_std only), and only
WebAssembly 1.0 features, for hosts that cannot handle newer proposals. And wasm32-unknown-emscripten assumes an
Emscripten-generated JavaScript runtime, which emulates a POSIX environment in the browser.
Step 1 — install and build for the candidates
Adding targets is cheap, and building the same crate for two of them is the fastest way to see the difference:
rustup target add wasm32-unknown-unknown wasm32-wasip1 wasm32-wasip2
cargo build --release --target wasm32-unknown-unknown
cargo build --release --target wasm32-wasip1
wasm-objdump -x -j Import target/wasm32-wasip1/release/app.wasm | head
Import[4]:
- func[0] sig=2 <- wasi_snapshot_preview1.fd_write
- func[1] sig=4 <- wasi_snapshot_preview1.environ_get
- func[2] sig=4 <- wasi_snapshot_preview1.environ_sizes_get
- func[3] sig=5 <- wasi_snapshot_preview1.proc_exit
The same crate built for wasm32-unknown-unknown has no such imports — or only the wasm-bindgen placeholders. That
import list is the contract with the host: a module can only run where every import it declares is provided.
Step 2 — decide by where the module runs
For a browser module called from JavaScript, use wasm32-unknown-unknown with wasm-bindgen. It produces the
smallest output, has the most mature tooling for the JavaScript boundary, and is what wasm-pack, Trunk and every Rust
UI framework target. The missing standard I/O rarely matters, because in a browser you want web-sys calls anyway.
For a command-line tool, a server-side function on an edge platform, or a module run by Node’s node:wasi, use
wasm32-wasip1. Ordinary Rust code — reading files, printing, parsing arguments — works unchanged. Runtimes from
wasmtime to Cloudflare Workers support it.
For new server-side work that benefits from typed interfaces — an HTTP handler on wasi:http, a plugin defined by a
WIT world, components composed together — use wasm32-wasip2. It is the direction WASI is moving, and its
differences from preview 1 are covered in
WASI preview 1 vs preview 2.
For a host that only implements WebAssembly 1.0 — some blockchain VMs, older embedded interpreters — use
wasm32v1-none with #![no_std]. And use the Emscripten target only when you must link Rust with C or C++ code that
was itself built for Emscripten; for most mixed projects,
linking C and Rust objects into one module
on wasm32-unknown-unknown is simpler.
Step 3 — write code that compiles for more than one
Libraries often need to support several targets. Gate target-specific code on target_os and target_arch rather
than on the triple string:
#[cfg(all(target_arch = "wasm32", target_os = "unknown"))]
fn now_ms() -> f64 {
js_sys::Date::now() // browser: ask JavaScript
}
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
fn now_ms() -> f64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs_f64() * 1000.0
}
On wasm32-unknown-unknown, target_os is "unknown"; on the WASI targets it is "wasi", with target_env
distinguishing p1 from p2. Put browser-only dependencies such as js-sys behind a target-specific table in
Cargo.toml so WASI builds do not pull them in:
[target.'cfg(all(target_arch = "wasm32", target_os = "unknown"))'.dependencies]
js-sys = "0.3"
wasm-bindgen = "=0.2.93"
Step 4 — check which proposals the target enables
Targets also differ in which post-MVP WebAssembly features the compiler uses by default. Recent Rust releases enable
bulk-memory, mutable-globals, sign-ext, nontrapping-fptoint, multivalue and reference-types for the
wasm32 targets, following LLVM’s defaults. Every current browser and major runtime supports them, but an older or
minimal host may not — which is the reason wasm32v1-none exists.
rustc --print target-features --target wasm32-unknown-unknown | grep -E '^\s+(bulk|simd|reference|multivalue)'
rustc --print cfg --target wasm32-unknown-unknown -C target-cpu=generic | grep target_feature
Features can be turned on or off per build with -C target-feature, for example +simd128 to allow SIMD
instructions. Detecting what a host supports before loading the module is covered in
detecting proposal support at runtime.
Step 5 — know what fails silently on unknown-unknown
The biggest trap of wasm32-unknown-unknown is that code compiles fine and then misbehaves at runtime. The standard
library’s platform functions exist as stubs:
let t = std::time::Instant::now(); // panics: "time not implemented on this platform"
let f = std::fs::read("config.toml"); // returns Err(Unsupported)
println!("hello"); // goes nowhere — no stdout
std::thread::spawn(|| {}); // returns an error / panics
None of these is a compile error, which is why a crate that “supports wasm32” may still not work in a browser. The remedies — browser-backed alternatives, feature flags, and finding which dependency calls what — are covered in fixing crates that fail to compile for Wasm and logging from Rust Wasm to the browser console.
Changing targets later
The target is not a one-way door, and it is worth knowing what moving between them costs. Moving a browser crate from
wasm32-unknown-unknown to a WASI target means replacing every wasm-bindgen import with a WASI or component interface —
the computational core moves unchanged, the boundary layer is rewritten. Moving a CLI tool from wasip1 to wasip2
is mostly a rebuild, because the standard library hides the difference, plus updating how the host runs it. Moving
anything to wasm32v1-none means giving up std, which is a real port.
That is a good argument for keeping the boundary layer thin and separate from day one. A crate structured as a pure
core library plus a small target-specific adapter — a #[wasm_bindgen] module for the browser, a main.rs for WASI —
can add a second target in an afternoon. A crate with web_sys calls spread through its business logic cannot.
Expected output
The same “hello world” crate built for each target shows the different environments clearly:
target size imports
wasm32-unknown-unknown 14 KB (none)
wasm32-wasip1 71 KB wasi_snapshot_preview1.* (4)
wasm32-wasip2 92 KB wasi:cli/*, wasi:io/* (component)
wasm32v1-none (no_std) 0.4 KB (none)
Running the WASI build prints to the terminal; the unknown-unknown build prints nothing at all, because println!
has nowhere to go.
Gotchas
error[E0463]: can't find crate for stdonwasm32v1-none. That target has no standard library. Add#![no_std]and usecoreandallocwith your own allocator.- A WASI module fails in the browser with unknown imports. Browsers do not implement WASI. Use a shim as in
running WASI modules in the browser,
or build for
unknown-unknown. - The old
wasm32-wasiname. It was renamed towasm32-wasip1and removed in Rust 1.84. Update scripts and CI. - A
wasip2component will not instantiate withWebAssembly.instantiate. Components are not core modules. Transpile with jco for JavaScript hosts, or run in a component-aware runtime.
Performance note
Target choice affects size far more than speed. The WASI hello world is five times larger than the
unknown-unknown one because println! pulls in formatting and the WASI I/O layer; the no_std build is tiny because
it contains almost nothing. Compute-heavy code runs at the same speed on every target — the instructions are the same.
Frequently Asked Questions
Can one crate target the browser and WASI from the same source?
Yes, with cfg gates for the platform-specific parts. Many libraries do exactly that, and the computational core is
usually target-independent anyway.
Is wasm64 available?
wasm64-unknown-unknown exists as a tier 3 target for the memory64 proposal. It is useful for very large heaps and
not yet a default choice; see Memory64 and large heaps.
Which target does Cloudflare Workers use?
Rust Workers use wasm32-unknown-unknown with wasm-bindgen through the worker crate; Workers’ WASI support covers
other cases. See deploying Wasm to Cloudflare Workers.
Does the target affect threads?
Threads need the atomics and bulk-memory features and a rebuilt standard library on unknown-unknown; the
wasm32-wasip1-threads target provides them for WASI hosts that support the threads proposal.
Related
- Compiling Rust to wasm32-wasip1 — the WASI target in practice.
- Building a component in Rust with cargo-component — producing preview 2 components.
- Choosing between Emscripten, wasm-pack, and TinyGo — the toolchain decision one level up.
- Pinning Wasm toolchain versions — listing targets in
rust-toolchain.toml.
← Back to Rust to Wasm Compilation Guide