Why Rust Bindgen Ignores Functions for WASI (and How to Fix It)
When compiling C code for WebAssembly via WASI, Rust is often considered one of the top choices for interoperability. However, many developers encounter a frustrating roadblock: running bindgen natively works as expected, but passing --target=wasm32-wasi or --target=wasm32-wasip1 results in a completely blank output file with all function signatures omitted.
If you have hit this issue, do not worry—Rust does not offer fewer capabilities than C. The issue stems from how bindgen delegates AST parsing to your local installation of libclang. Here is a breakdown of why this happens and how to resolve it.
The Core Problem: Libclang Cross-Compilation and Missing Sysroots
bindgen is not an independent C parser; it is a Rust wrapper around LLVM’s libclang. When you run bindgen without target flags, it uses your host machine’s default target and system headers. Everything resolves cleanly.
However, when you specify a cross-compilation target like wasm32-wasip1 or wasm32-wasi, two things often go wrong:
- Missing WASI Sysroot: Unlike your native platform, the WebAssembly target does not have default system header paths installed in standard OS directories. If Clang cannot locate a standard library or basic type definitions for the target architecture, it fails to parse the translation unit and quietly exits with an empty AST.
- Target Triple Compatibility: The target
wasm32-wasip1was introduced in LLVM 18 and Rust 1.78. If your installed version oflibclangis older, it will not recognizewasm32-wasip1as a valid target, causing parsing to fail immediately.
Step-by-Step Solutions
1. Supply the WASI SDK Sysroot
If you are building for WASI, you must install the WASI SDK and explicitly pass the sysroot to bindgen via Clang arguments. Download the WASI SDK, extract it, and pass --sysroot after the trailing double dash --:
bindgen minimal.h -o minimal_wasm.rs -- \
--target=wasm32-wasi \
--sysroot=/path/to/wasi-sdk-20.0/share/wasi-sysrootPassing the sysroot ensures Clang can resolve target-specific primitive types (such as int, size_t, and standard definitions) without aborting the AST build.
2. Match Your Clang Target to Your LLVM Version
Ensure that the target triple you pass to Clang is supported by the libclang library that bindgen is linked against:
- For LLVM 17 and earlier, use
--target=wasm32-wasi. - For LLVM 18 and newer, use
--target=wasm32-wasip1.
You can check the version of Clang that bindgen uses by checking clang --version in your terminal or checking the LIBCLANG_PATH environment variable.
3. Configure via build.rs (Recommended Approach)
Invoking bindgen through the command-line interface can be fragile across different environments. The recommended and idiomatic way in Rust is to use bindgen as a build dependency in a build.rs script:
// build.rs
use std::env;
use std::path::PathBuf;
fn main() {
let mut builder = bindgen::Builder::default()
.header("minimal.h")
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
let target = env::var("TARGET").unwrap_or_default();
if target.contains("wasi") {
let wasi_sysroot = env::var("WASI_SYSROOT")
.expect("Please set WASI_SYSROOT environment variable");
builder = builder
.clang_arg(format!("--target={}", target))
.clang_arg(format!("--sysroot={}", wasi_sysroot));
}
let bindings = builder.generate().expect("Unable to generate bindings");
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
4. Diagnose Silent Failures with Verbose Logging
If your bindings file is still empty, turn on bindgen debug logging to see the exact Clang error that causes the generation to abort. In your shell, set the RUST_LOG environment variable before running the command:
export RUST_LOG=bindgen=debug
bindgen minimal.h -o minimal_wasm.rs -- --target=wasm32-wasiInspect the output for lines containing clang diagnostic or fatal header inclusion failures. This will pinpoint the missing include directories or unsupported compilation flags immediately.
Conclusion
Rust does not restrict your access to C functions on WebAssembly. The empty output occurs because bindgen relies on libclang, which demands a complete sysroot environment when cross-compiling to bare-metal or WASI targets. By pointing Clang to the WASI SDK sysroot and aligning your target flags, you can generate flawless FFI bindings for wasm3, Wasmtime, or any other WASI runtime.