# Background
Wit-bindgen is a language bindings generator for wit. And wit is the abbreviation of WebAssembly Interface Types. Here's an excellent article explaining what is WebAssembly Interface Types (opens new window). In short, wit enables wasm components to work with each other better, with rich apis and complex types.
In this post, I will try to introudce how to use wit-bindgen. The source code used in this post can be found in this repository (opens new window).
# What can wit bindgen do?
wit-bindgen
generate bindings for various languages from *.wit
files, which defines the general interface of the wasm module.
With the interface defined in *.wit
, we can implement that interface in one language, say Rust, and compile it into WASM.
With the same wit
file, we can import the compiled WASM module into other languages like python, js, etc. And it just works. We don't need to concern about the glue code, they are generated by wit-bindgen
for us.
# A quick demo.
# Define the wit file
First, we create a file markdown.wit
like this:
interface exports {
render: func(input: string) -> string
}
world markdown {
default export exports
}
In this wit
file, we define a function that renders a markdown string to html string.
A world is more like a component which can import and export functions. This defines the API for this component.
# Implement that in Rust
In the lib file, we use pulldown_cmark
to parse the markdown string.
wit_bindgen_guest_rust::generate!("./wits/markdown.wit");
use pulldown_cmark::{html, Parser};
struct Markdown;
export_markdown!(Markdown);
impl markdown::Markdown for Markdown {
fn render(input: String) -> String {
let parser = Parser::new(&input);
let mut html_output = String::new();
html::push_html(&mut html_output, parser);
return html_output;
}
}
Then compile it to wasm with cargo b --release --target wasm32-unknown-unknown
.
Note that export_markdown!(Markdown);
is necessary, otherwise you will get a error, complaining that render is not exported
.
# Use the component in Rust
Then we can use this component in Rust, following the code below.
// generate bindings.
wit_bindgen_host_wasmtime_rust::generate!("./wits/markdown.wit");
use wit_bindgen_host_wasmtime_rust::anyhow::{self, Result};
use wit_bindgen_host_wasmtime_rust::wasmtime::{
self,
component::{Component, Linker},
Config, Engine, Store,
};
// Acutally, when you run `cargo b` in the last step, you will get a wasm module,
// not a wasm component, so we need to use this ComponentEncoder to transform the
// wasm module to component.
use wit_component::ComponentEncoder;
fn main() -> Result<()> {
let mut config = Config::new();
// Enable component here.
config.wasm_component_model(true);
let engine = Engine::new(&config)?;
let mut store = Store::new(&engine, 0);
let linker = Linker::new(&engine);
// we first read the bytes of the wasm module.
let module = std::fs::read("./target/wasm32-unknown-unknown/release/app.wasm")?;
// then we transform module to compoennt.
// remember to get wasi_snapshot_preview1.wasm first.
let component = ComponentEncoder::default()
.module(module.as_slice())?
.validate(true)
.adapter_file(std::path::Path::new("./wits/wasi_snapshot_preview1.wasm"))?
.encode()?;
let component = Component::from_binary(&engine, &component)?;
// after getting the component, we can instantiate a markdown instance.
let (markdown, _instance) = Markdown::instantiate(&mut store, &component, &linker)?;
let res = markdown.render(&mut store, "# hello")?;
assert_eq!(res, "<h1>hello</h1>\n");
Ok(())
}
# Use the Compoennt in Python
Wait, how can I use the wasm compoennt in Python? It is not that straightforward.
First, we need to install wasmtime
for python.
Since python support in wit-bindgen-cli
has been moved to wasmtimepy
(opens new window), we don't need to install wit-bindgen-cli
anymore.
# Install wasmtime for python.
pip3 install wasmtime
Then, we can generate the bindings, from the component.wasm
, not component.wit
anymore.
So let's save the component.wasm
using rust first.
// we first read the bytes of the wasm module.
let module = std::fs::read("./target/wasm32-unknown-unknown/release/app.wasm")?;
// then we transform module to compoennt.
// remember to get wasi_snapshot_preview1.wasm first.
let component = ComponentEncoder::default()
.module(module.as_slice())?
.validate(true)
.adapter_file(std::path::Path::new("./wits/wasi_snapshot_preview1.wasm"))?
.encode()?;
std::fs::write("./target/component.wasm", &component)?;
# generate bindings
python -m wasmtime.bindgen ./target/component.wasm --out-dir python/markdown/
After all of these, we can create a python file to use the component.
from wasmtime import Engine, Store, Config
from markdown import Component
if __name__ == "__main__":
text = "# head"
config = Config()
engine = Engine(config)
store = Store(engine)
component = Component(store)
result = component.render(store, text)
assert result == "<h1>head</h1>\n"
print(result)
That's it! Using python is eaiser, but you have to deal with other things. I prefer to use rust, cause it's a end to end solution.
# Where to get wasi_snapshot_preview1.wasm
?
The source code of wasi_snapshot_preview1
is located in the repo of wit-bindgen
.
Just clone the source code and build it.
The following script may help with this.
fn main() {
let mut cmd = std::process::Command::new("cargo");
cmd.arg("build")
.arg("--release")
.current_dir("../wasi_snapshot_preview1")
.arg("--target=wasm32-unknown-unknown")
.env("CARGO_TARGET_DIR", "./target")
.env(
"RUSTFLAGS",
"-Clink-args=--import-memory -Clink-args=-zstack-size=0",
)
.env_remove("CARGO_ENCODED_RUSTFLAGS");
let status = cmd.status().unwrap();
assert!(status.success());
}
wasi_snapshot_preview1.wasm
will be generated at wit-bindgen/crates/wasi_snapshot_preview1/wasm32-unknown-unknown/release
.