Search code examples
crustwebassemblywasm-bindgen

How you can link a Rust library with C/C++ and wasm-bindgen for the wasm32-unknown-unknown target?


Title.

What are the specific steps to compile and link C/C++ and Rust under the same WASM binary for wasm32-unknown-unknown while using wasm-bindgen?

Context

  • wasm32-unknown-emscripten is a legacy target and seldom maintained by one part-time person, but until recently, it was the only target capable of compiling and linking libraries that use both Rust and C.
  • The Rust+WASM community largely moved towards the wasm32-unknown-unknown target with close collaboration with the wasm-bindgen project. A big part of the Rust+WASM ecosystem, including winit and wgpu gravitates towards that. There's good support and documentation all around.

Problem

The Rust compiler used to produce an incompatible C ABI for the wasm32-unknown-unknown target, that does not follow the specs-compliant clang's C ABI.

As wasm-bindgen depends on the Rust compiler, it also produced a WASM binary that was fundamentally incompatible and could not be linked together with binaries created by compliant C compilers.

Current State of the Ecosystem

There has been significant progress towards a solution. In April 2024, the Rust compiler team introduced a perma-unstable --wasm_c_abi flag that tells the compiler to produce compliant C ABI for WASM targets, and wasm-bindgen started to support compliant C ABI starting from version 0.2.88. This is the relevant tracking issue.

This flag is available in nightly Rust and will be removed in a future version, where the compiler will produce compliant C ABI by default.


Solution

  • Up until April 2024, you needed to use wasm32-unknown-emscripten or wasm32-wasi targets to link C and Rust under the same binary.

    Now that the Rust compiler team Merged a PR introducing the perma-unstable wasm_c_abi flag, it's now possible to compile Rust+C for the wasm32-unknown-unknown target with Rust nightly and use wasm-bindgen with external C dependencies. This will be the default behavior in the near future.

    Here's a minimal example of a Rust+C WASM lib.

    The build process boils down to:

    export RUSTFLAGS="--cfg=web_sys_unstable_apis --Z wasm_c_abi=spec"
    cargo +nightly build --target=wasm32-unknown-unknown --release
    
    cp target/wasm32-unknown-unknown/release/hello.rlib hello.a
    
    clang \
        --target=wasm32 \
        -c \
        -o world.o \
        world.c
    
    wasm-ld \
        --no-entry \
        --export-all \
        -o hello_world.wasm \
        hello.a \
        world.o