I’m a Senior Software Engineer living in Berlin. Shifting limits based on quality and robustness. Cutting-edge software development. Defining durable and flexible interfaces. Creating rich and intuitive user experiences.

Create and Compose WebAssembly Components: A Hands-On Example with Rust

The WebAssembly Component Model is an emerging standard that makes WebAssembly modules more interoperable and composable across languages and runtimes. Instead of passing raw memory buffers and function pointers between modules, the component model introduces a higher-level system of interfaces, imports, and exports defined using WIT (WebAssembly Interface Types).

In this article, we’ll walk through a concrete example: building a simple decider program in Rust that chooses between two numbers. The project is split into two components:

  1. Library component (first) – implements a decide interface.
  2. Command component (decider) – imports the decide interface and runs it as a CLI program.

Finally, we’ll compose the two into a single runnable WebAssembly artifact and execute it with Wasmtime.

Tooling Setup

We’ll use the Bytecode Alliance toolchain:

  • cargo-component: scaffolding, bindings, and building Rust components
  • wasm-tools: inspecting and validating Wasm modules
  • wac: wiring components together (plugging imports into exports)
  • wasmtime: runtime for Wasm components

Install them:

cargo install --locked cargo-component
cargo install --locked wasm-tools
cargo install --locked wac-cli
brew install wasmtime

Step 1: Library Component (first)

First, we’ll create a library component that provides a decide function.

# from the root folder
cargo component new first --lib && cd first

Defining the Interface

Edit wit/world.wit to define the decide interface:

package component:first;
interface decide {
decide: func(x: u32, y: u32) -> u32;
}
world first {
export decide;
}

Implementing in Rust

Update the bindings:

cargo component bindings

Then edit src/lib.rs:

#[allow(warnings)]
mod bindings;
use bindings::exports::component::first::decide::Guest;
struct Component;
impl Guest for Component {
fn decide(x: u32, _y: u32) -> u32 {
x
}
}

Build and inspect:

cargo component build --release
wasm-tools component wit target/wasm32-wasip1/release/first.wasm

Step 2: Command Component (decider)

Next, we’ll create a command component that imports the decide function and uses it in a CLI program.

cargo component new decider && cd decider

Define the World

Create wit/world.wit:

package component:decider;
world decider {
import component:first/decide;
}

Update Cargo.toml to wire the dependency:

[package.metadata.component.target]
path = "wit"
[package.metadata.component.target.dependencies]
"component:first" = { path = "../first/wit" }

Add CLI Parsing

Add clap:

cargo add clap -F derive

Update bindings:

cargo component bindings

Then modify src/main.rs:

#[allow(warnings)]
mod bindings;
use bindings::component::first::decide::decide;
use clap::Parser;
#[derive(Parser)]
struct Command {
x: u32,
y: u32,
}
impl Command {
fn run(self) {
let result = decide(self.x, self.y);
println!("{} or {} was decided to {result}", self.x, self.y);
}
}
fn main() {
Command::parse().run()
}

Build and check:

cargo component build --release
wasm-tools component wit target/wasm32-wasip1/release/decider.wasm

Step 3: Composition

Now we connect the command component (decider) to the library component (first) using wac plug.

# from the root folder
wac plug \
decider/target/wasm32-wasip1/release/decider.wasm \
--plug first/target/wasm32-wasip1/release/first.wasm \
-o final.wasm

Step 4: Run the Final Component

Finally, run it with Wasmtime:

wasmtime run final.wasm 2 1

Output:

2 or 1 was decided to 2

Conclusion

This small project demonstrates how to:

  1. Define a WIT interface (decide).
  2. Implement it in Rust with cargo-component.
  3. Import and use it in another component.
  4. Compose components with wac plug.
  5. Run the result in Wasmtime.

The WebAssembly Component Model brings structured composition and language interoperability to Wasm. Even in this toy example, we see how easily a reusable library component (first) can be cleanly integrated into another program (decider)—a foundation for building polyglot, portable systems.