A workspace of progressive examples showing how to produce a single WASM binary that combines Rust, C, and C++ code — from manual linking all the way to integrating real external libraries with a full C/C++ standard library.
As of Rust v1.89 (August 2025), the wasm32-unknown-unknown target officially uses the standards-compliant C ABI by default. This means C and Rust code compiled to WASM can call each other without any special flags.
For historical context, see the relevant tracking issue in wasm-bindgen and the official Rust blog post about this change.
The crates experiment with different build strategies, with increasing levels of complexity:
-
Linking Manually — A simple calculator with primitive data types and manual build using
wasm-ld. -
With CC Crate — The same calculator built with the CC crate.
All crates from 3 onwards use musl libc (v1.2.6) compiled for wasm32-unknown-unknown, providing a full C standard library (malloc/free, printf/snprintf, string operations, etc.).
-
Libc and Heap — Adds heap allocation (
malloc/free) via musl libc. -
Wasm Bindgen — We create a Calculator struct with member functions and export it with wasm-bindgen.
-
Rust Bindgen — Same as above, but we generate the Rust bindings from the C header with Rust Bindgen.
-
Extern Types — Experiments with the nightly feature
extern types. -
Musl Libc (full demo) — A more complete calculator that exercises musl's
snprintffor formatted output. Demonstrates the pattern used in production for integrating real-world C libraries. -
LLVM Libc++ — Adds LLVM's libc++ on top of musl libc, enabling C++ standard library features (
std::vector,std::string,<algorithm>, etc.) in WASM. The C++ Calculator class demonstrates how to wrap C++ objects with a C API for Rust FFI. Build configuration mirrors emscripten's system_libs.py. -
Capstone: zlib — Integrates a real, unmodified external C library (zlib) compiled from source. Demonstrates the full production pattern: external C library + musl libc + safe Rust wrappers + wasm-bindgen export.
No setup required. Just build:
cd crates/1_linking_manually && ./build.shInitialize the git submodules first, then build:
# Initialize submodules (musl, emscripten libcxx, zlib)
./setup.sh
# Build everything
./build_all.sh
# Or build a specific crate
cd crates/4_wasm_bindgen && ./build.shAll examples require LLVM, Clang, Rust (stable), and wasm-pack. Crate 6 (extern_types) additionally needs the nightly toolchain — its own rust-toolchain.toml selects it automatically when you cd into that crate.
For inspecting outputs: Wasm Binary Toolkit (wasm2wat).
To see the examples in action, use your favorite local server:
npx serveThen visit http://localhost:3000 and click the example you want to see.
The 3rd-party/ directory contains two Rust crates that compile and link the C/C++ standard libraries for wasm32-unknown-unknown:
3rd-party/
├── wasm32-libc/ # Musl libc compiled for wasm32
│ ├── musl/ # ← git submodule (https://git.musl-libc.org/cgit/musl)
│ ├── src/ffi/ # Rust implementations of C stdlib functions
│ └── build.rs # Compiles musl + generates Rust bindings
│
└── wasm32-libcxx/ # LLVM libc++ compiled for wasm32
├── emscripten/ # ← git submodule (https://github.com/emscripten-core/emscripten)
└── build.rs # Compiles libcxx + libcxxabi from emscripten sources
The wasm32-libcxx crate depends on wasm32-libc (C++ needs a C library underneath). Consumer crates declare these as Cargo dependencies, and header paths are propagated via Cargo's DEP_*_INCLUDE environment variables.
zlib_compression (Rust crate)
├── zlib (external C library, compiled from source)
├── wasm32-libc (musl libc for wasm32)
└── wasm-bindgen (JS interop)
If you'd like to see any other scenario listed here, feel free to open an Issue or a PR.
If submitting a new example, create a numbered subfolder in the crates directory following the existing structure, and ensure your example builds correctly for both WASM and unit tests.
Finally, run cargo clippy and stick with the default rules. CI runs cargo fmt --check, cargo clippy -D warnings, cargo test, and ./build_all.sh on every pull request — see .github/workflows/ci.yml.