When targeting WebAssembly, C/C++ code can be compiled as a library, and then get statically linked to a Rust project.
Install the Zig toolchain in order to compile C and C++ code to WebAssembly.
zig cc
is available for many platforms including Windows, and makes it easy to switch back and forth between native and wasm targets. WebAssembly is a Tier-1 target, and it was successfully used to port libraries such as ffmpeg
, zlib
, openssl
, boringssl
and libsodium
.
Compile individual files with zig cc -target wasm32-freestanding
or zig cc -target wasm32-wasi
instead of cc
:
zig cc -target wasm32-wasi -Os -c example.c
Here's an example C file:
int add(int a, int b)
{
return a + b;
}
The wasm32-freestanding
target works everywhere out of the box, including web browsers, whereas wasm32-wasi
requires an additional JavaScript helper, or a runtime that supports WASI-Core.
Runtime-specific optimizations can be enabled with the -mcpu
command-line flag, for example -mcpu=generic+simd128
. Optimizations apply both to application/libraries being compiled and to the C library.
Link the object files into a static library, by using the zig ar
command instead of the ar
command:
zig ar r libexample.a *.o
Congratulations! The WebAssembly library has been created.
An alternative to steps 2 and 3 is to build the project using the Zig build system, by creating a build.zig
file, and then compile with zig build -Dtarget=wasm32-wasi -Doptimize=ReleaseSmall
. Examples: libhydrogen
, libaegis
.
Create a new Rust project, as well as the following files in its root directory:
rust-toolchain
with the following content:
[toolchain]
targets = ["wasm32-wasi"]
build.rs
with the following content:
use std::env;
pub fn main() {
let src_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
println!("cargo:rustc-link-lib=static=example"); // name of the wasm static library to link
println!("cargo:rustc-link-search=native={}/wasm-libs", src_dir); // directory of the wasm static library
}
Create a wasm-libs
directory, and copy the libexample.a
file into that directory, so that the Rust project can find it.
Use C functions using the Rust FFI interface:
use core::ffi::*;
extern "C" {
pub fn add(a: c_int, b: c_int) -> c_int;
}
fn main() {
println!("{}", unsafe { add(1, 2) });
}
Compile the Rust code:
cargo build --target=wasm32-wasi
Or install the cargo-wasix
Cargo subcommand.