How to Fix: Error `assertion left == right failed` on Wasm module init in Rust-embedded Wasmtime
That assertion left == right failed panic during Wasm startup usually means your Rust host and the compiled WebAssembly module disagree about which WebAssembly proposals are enabled—most commonly Wasm GC, target features, or a version mismatch between the module and Wasmtime.
Table of Contents
In this Rust-embedded Wasmtime scenario, the issue often appears when a Kotlin-generated Wasm module expects Wasm GC support, but the host engine is either not enabling the required feature flags correctly or is validating/instantiating the module with a configuration that does not match the module's encoded capabilities.
Understanding the Root Cause
The panic is not usually caused by Kotlin alone. It is typically triggered by a mismatch in one of these layers:
- Wasmtime version is too old for the module features being used.
- Engine configuration does not explicitly enable required proposals such as gc.
- Crate version skew exists between
wasmtime,wasmtime-wasi, or related dependencies. - The module was compiled with newer proposal expectations than the embedded runtime supports.
- Debug assertions inside Wasmtime or Cranelift surface an internal invariant failure when unsupported or partially enabled features are encountered.
For Kotlin/Wasm specifically, newer toolchains can emit modules that rely on reference types, typed function references, and especially garbage-collected heap types. Even if Wasmtime 27+ officially includes Wasm GC support, that does not guarantee your embedded application enables the right configuration by default.
In practice, the assertion failure happens because the module contains type metadata or runtime expectations that the engine validates differently unless GC-related support is turned on in the Config used to create the Engine.
Step-by-Step Solution
The fix is to align all three pieces: the Kotlin output, the Wasmtime crate versions, and the embedded engine configuration.
1. Upgrade Wasmtime and related crates together
Make sure all Wasmtime crates use the same major and minor version.
[dependencies]
wasmtime = "27.0.0"
wasmtime-wasi = "27.0.0"
anyhow = "1"
If possible, use an even newer stable Wasmtime release instead of pinning to the first version that introduced the feature.
2. Enable Wasm GC in the embedded engine
Create your engine with an explicit configuration instead of relying on defaults.
use anyhow::Result;
use wasmtime::{Config, Engine, Module, Store};
fn main() -> Result<()> {
let mut config = Config::new();
config.wasm_reference_types(true);
config.wasm_function_references(true);
config.wasm_gc(true);
let engine = Engine::new(&config)?;
let module = Module::from_file(&engine, "module.wasm")?;
let mut store = Store::new(&engine, ());
let instance = wasmtime::Instance::new(&mut store, &module, &[])?;
println!("Wasm module instantiated successfully: {:?}", instance);
Ok(())
}
If your exact Wasmtime version uses slightly different method names or feature availability, check the corresponding API docs for that version and keep the same idea: enable GC and related reference features explicitly.
3. Verify the module actually uses GC-related features
Inspect the generated Wasm to confirm it contains the expected proposal usage. Tools such as WABT or WebAssembly text tooling can help.
wasm-objdump -x module.wasm
wasm2wat module.wasm -o module.wat
Look for signs of struct, array, heap types, or advanced type section entries associated with GC-enabled Wasm.
4. Rebuild the Kotlin/Wasm artifact with a compatible toolchain
If the module was produced by an experimental Kotlin/Wasm compiler version, regenerate it using a toolchain known to target the Wasmtime feature level you are embedding.
Also verify whether Kotlin emits assumptions better aligned with browsers than standalone runtimes. A browser-compatible Wasm binary is not automatically guaranteed to work unchanged in an embedded host unless the same proposals are available.
5. Turn on backtraces and validate early
Capture more context before instantiation.
RUST_BACKTRACE=1 cargo run
And validate/load the module as early as possible:
use anyhow::Result;
use wasmtime::{Config, Engine, Module};
fn validate_module(path: &str) -> Result<()> {
let mut config = Config::new();
config.wasm_reference_types(true);
config.wasm_function_references(true);
config.wasm_gc(true);
let engine = Engine::new(&config)?;
let bytes = std::fs::read(path)?;
Module::validate(&engine, &bytes)?;
println!("Module validation passed");
Ok(())
}
If validation fails before instantiation, the problem is almost certainly a feature compatibility mismatch rather than your host imports.
6. Eliminate dependency and feature drift
Run dependency inspection to ensure only one Wasmtime line is present.
cargo tree | grep wasmtime
If you see mixed versions, unify them in Cargo.toml and rebuild from a clean state.
cargo clean
cargo build
7. If the panic persists, test the module in the Wasmtime CLI
This helps determine whether the problem is in your Rust embedding code or the module itself.
wasmtime run module.wasm
If the CLI also fails, the issue is likely the binary or proposal compatibility. If the CLI works but your embedded host fails, your Rust engine configuration is the main suspect.
Common Edge Cases
- Using Wasmtime 27 in one crate and another version transitively elsewhere: this can create confusing runtime behavior or compile-time API mismatches.
- Only enabling gc without related reference features: some modules need the broader feature set around references and function references.
- Kotlin compiler output changed between releases: a newer compiler may emit Wasm that depends on proposal details not fully stabilized across runtimes.
- Host import mismatches: once validation passes, instantiation can still fail if imports, memories, or exported signatures do not line up.
- Running release vs debug builds: debug assertions can expose internal failures more aggressively. If debug panics but release behaves differently, that still indicates a real compatibility issue worth fixing.
- Assuming browser support equals Wasmtime support: browser engines and standalone runtimes may differ in proposal rollout timing and implementation details.
FAQ
Why does this happen even though Wasmtime 27+ supports Wasm GC?
Because support in the runtime does not always mean your embedded Engine enables the needed features by default. Your module, engine config, and crate versions must all agree.
How do I know whether the problem is the module or my Rust embedding code?
First run Module::validate with GC enabled. Then test the same file with the Wasmtime CLI. If both fail, the module or its feature requirements are the issue. If only your app fails, inspect your engine configuration and imports.
Do I need to enable only wasm_gc(true)?
Usually you should also enable related features such as reference types and function references when dealing with Kotlin-generated Wasm. The exact combination depends on the Wasmtime version and the module's emitted features.
The most reliable fix is simple: upgrade Wasmtime consistently, enable GC-related proposals explicitly in Config, validate the module before instantiation, and confirm the Kotlin-generated binary matches the feature level your embedded runtime actually supports.