A WebAssembly runtime for running component-model based WASM modules.
- 🐳 Docker-like Architecture: Image/Container model for managing WASM modules
- 🔄 Generic Runtime: Does not prescribe any specific WIT interface - users define their own
- 🎯 Flexible: Builder pattern for custom WIT bindings and host state
- 🔌 WIT-based: Type-safe communication via WebAssembly Interface Types
- 🦀 Pure Rust: Built on Wasmtime with the Component Model
- 📦 Macros: Helper macros to reduce boilerplate
- 🚀 Dynamic Invocation: Runtime WASM function calls with RON and binary canonical ABI
- 🔤 RON Support: Rust-friendly serialization for better type compatibility
- ⚡ Dual Calling Paths: RON (convenient) + Binary (high-performance) for dynamic invocation
This project uses just as a build system.
Tairitsu provides several examples demonstrating different approaches to WASM integration. Run them using just:
# Run comprehensive tests
just testFor all available commands and their descriptions, see the justfile.
The easiest way to define WIT interfaces using procedural macros:
Features:
- Zero boilerplate - automatic enum generation from WIT-like syntax
- Compile-time type safety with full IDE support
- No runtime serialization overhead
- Simple and intuitive API
wit_interface! {
interface filesystem {
read: func(path: String) -> Result<Vec<u8>, String>;
write: func(path: String, data: Vec<u8>) -> Result<(), String>;
delete: func(path: String) -> Result<(), String>;
list: func(directory: String) -> Result<Vec<String>, String>;
}
}Manual trait implementation for maximum flexibility:
Features:
- Full control over interface definitions
- Composable interfaces via traits
- Zero-cost abstractions
- Interface extension support
Advanced dynamic invocation with actual WASM Components:
Features:
- 🚀 Runtime guest export calls (RON + Binary)
- 📥 Host import registration and invocation
- 🔍 Runtime function discovery
- 🔤 RON serialization for Rust-friendly types
- ⚡ Binary canonical ABI for high performance
- ✅ Full support for basic and complex nested types
Static WIT binding with wasmtime bindgen:
Features:
- Complete compile-time type checking
- Zero runtime overhead
- Best performance
- IDE autocomplete support
Dynamic WIT definition loading:
Features:
- Runtime WIT discovery
- Interface validation
- Plugin system support
- Capability negotiation
See examples/README.md for detailed documentation of each example.
| Approach | Type Safety | Performance | Flexibility | Best For |
|---|---|---|---|---|
| Macro | Full | Best | Medium | Most use cases |
| Trait | Full | Best | High | Complex interfaces |
| Dynamic RON/WASM | Runtime | Best (Binary) | Highest | Plugin systems, hot-reload |
| Compile-time | Full | Best | Low | Fixed interfaces |
| Runtime | Partial | Good | High | Plugin systems |
use tairitsu::{Container, Image, HostState};
use bytes::Bytes;
// 1. Create a WASM image
let wasm_binary = std::fs::read("component.wasm")?;
let image = Image::new(Bytes::from(wasm_binary))?;
// 2. Create container with your WIT bindings
let container = Container::builder(image)?
.with_guest_initializer(|ctx| {
// Register your WIT interface (generated by wit-bindgen)
MyWit::add_to_linker(ctx.linker, |state| &mut state.my_data)?;
// Instantiate the component
let instance = MyWit::instantiate(
ctx.store,
ctx.component,
ctx.linker
)?;
Ok(GuestInstance::new(instance))
})?
.build()?;
// 3. Use the container to call into WASM
let guest = container.guest().downcast_ref::<MyWit>()?;
let result = guest.my_function(ctx.store)?;Tairitsu is a generic WASM runtime engine - it does not prescribe any specific WIT interface:
graph TB
subgraph Registry["Registry"]
direction LR
Image1["Image"]
Image2["Image"]
Image3["Image"]
end
subgraph Containers["Containers"]
Container1["Container"]
Container2["Container"]
end
subgraph Container1["Container"]
direction TB
Guest["Guest (WASM)"]
Host["Host"]
Guest <-->|"User Defined<br/>Communication"| Host
end
Registry --> Containers
Image1 -.->|"Compiled WASM components"| Container1
Image2 -.->|"Compiled WASM components"| Container1
Image3 -.->|"Compiled WASM components"| Container2
style Registry fill:#e1f5ff
style Containers fill:#fff4e1
style Container1 fill:#f0e1ff
style Container2 fill:#f0e1ff
style Guest fill:#e1ffe1
style Host fill:#ffe1f0
- Registry: Manages WASM images and running containers (like Docker daemon)
- Image: A compiled WASM component that can be instantiated (like Docker image)
- Container: A running instance with user-defined WIT bindings
- WIT Bindings: Users define their own WIT interfaces using
wit-bindgen
- Create a
.witfile:
interface my-app {
greet: func(name: string) -> string;
compute: func(input: string) -> string;
}- Generate bindings with
wit-bindgen:
wit-bindgen rust --world my-app- Use the generated bindings with Tairitsu:
use tairitsu::Container;
let container = Container::builder(image)?
.with_guest_initializer(|ctx| {
// Use the generated bindings
MyApp::add_to_linker(ctx.linker, |state| &mut state.data)?;
let instance = MyApp::instantiate(ctx.store, ctx.component, ctx.linker)?;
Ok(GuestInstance::new(instance))
})?
.build()?;Tairitsu provides macros to reduce boilerplate:
Quickly implement the WitInterface trait:
impl_wit_interface!(MyInterface, "my-interface",
fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
dispatcher.register("my-command", Box::new(my_handler));
}
);Create stateless handlers:
let handler = simple_handler!(MyCommand, |cmd| {
match cmd {
MyCommand::Foo => Ok(()),
}
});Create handlers with state:
let handler = stateful_handler!(MyState, MyCommand, |state, cmd| {
state.counter += 1;
Ok(state.counter)
});The native approaches (wit-native-*) provide compile-time type safety without runtime serialization overhead:
use tairitsu::{WitCommand, WitCommandHandler, WitCommandDispatcher};
// Define your command types
#[derive(Debug, Clone)]
pub enum MyCommands {
Process { input: String },
Query { id: u32 },
}
impl WitCommand for MyCommands {
type Response = Result<String, String>;
fn command_name(&self) -> &'static str {
match self {
Self::Process { .. } => "process",
Self::Query { .. } => "query",
}
}
fn as_any(&self) -> &dyn std::any::Any {
self
}
}
// Implement handler
struct MyHandler;
impl WitCommandHandler<MyCommands> for MyHandler {
fn execute(
&mut self,
command: &MyCommands,
) -> Result<MyCommands::Response, String> {
match command {
MyCommands::Process { input } => {
Ok(Ok(format!("Processed: {}", input)))
}
MyCommands::Query { id } => {
Ok(Ok(format!("Query result for {}", id)))
}
}
}
}For scenarios requiring both Rust-friendly types and high performance:
use tairitsu::{ron::{typed_ron_tool, RonToolRegistry}, Container, Image};
// RON provides Rust-native type support
#[derive(Deserialize, Serialize)]
struct CalculatorRequest {
a: i32,
b: i32,
}
#[derive(Deserialize, Serialize)]
struct CalculatorResponse {
result: i32,
}
// Create a type-safe tool with RON
let tool = typed_ron_tool("calculator", |input: CalculatorRequest| -> CalculatorResponse {
CalculatorResponse {
result: input.a + input.b,
}
});
// Register and invoke with RON syntax
let mut registry = RonToolRegistry::new();
registry.register("calculator".to_string(), tool);
// RON uses Rust-like syntax
let result = registry.invoke("calculator", "(a: 42, b: 58)")?;
// For WASM Components, use the dynamic invocation API
let mut container = Container::builder(image)?
.with_guest_initializer(|ctx| {
// ... setup WIT bindings
Ok(GuestInstance::new(instance))
})?
.build()?;
// RON path - convenient and readable
let result = container.call_guest_raw_desc(
"add",
r#"(a: 42, b: 58)"#
)?;
// Binary path - high performance
use wasmtime::component::Val;
let args = vec![Val::S32(42), Val::S32(58)];
let results = container.call_guest_binary("add", &args)?;RON Type Support:
| Type | RON Syntax | Description |
|---|---|---|
| Struct | Struct { field: value } |
Named fields |
| Tuple | (value1, value2) |
Ordered values |
| Enum | Variant(value) |
Enum with payload |
| Option | Some(value) / None |
Optional values |
| Result | Ok(value) / Err(error) |
Fallible operations |
| List | [item1, item2] |
Homogeneous arrays |
| Unit | () |
Empty tuple |
Use trait objects to compose multiple WIT interfaces:
use tairitsu::{CompositeWitInterface, WitInterface};
struct FileSystemInterface;
impl WitInterface for FileSystemInterface {
fn interface_name(&self) -> &'static str {
"filesystem"
}
fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
// Register handlers
}
}
struct NetworkInterface;
impl WitInterface for NetworkInterface {
fn interface_name(&self) -> &'static str {
"network"
}
fn register_handlers(&self, dispatcher: &mut WitCommandDispatcher) {
// Register handlers
}
}
// Compose multiple interfaces
let mut composite = CompositeWitInterface::new();
composite.add_interface(Box::new(FileSystemInterface));
composite.add_interface(Box::new(NetworkInterface));
// Register all at once
let mut dispatcher = WitCommandDispatcher::new();
composite.register_all(&mut dispatcher);Tairitsu is designed to be a pure engine - it provides:
- ✅ Generic WASM container runtime
- ✅ Image/Registry management
- ✅ Builder pattern for WIT bindings
- ✅ Helper macros to reduce boilerplate
It does not prescribe:
- ❌ Specific WIT interfaces (you define your own)
- ❌ Command types (you define your own)
- ❌ Serialization format (use WIT)
This makes Tairitsu suitable for any WASM component-based application.
Native (Trait-based) - Use when:
- You control both host and guest code
- Performance is critical
- Type safety is a priority
- Building a closed system
Dynamic RON/WASM - Use when:
- Plugin systems with hot-reload required
- Performance-critical dynamic calls needed
- Rust-friendly serialization preferred
- Full bidirectional guest-host communication
Choosing Between RON and Binary Paths:
| Path | Type Safety | Performance | Use Case |
|---|---|---|---|
| RON | Runtime | Good | Convenient, debugging, cross-language |
| Binary | Runtime | Best | Hot loops, frequent calls, zero-copy |
| Static | Compile-time | Best | Known interfaces, maximum type safety |
- ✅ Quick development with minimal boilerplate
- ✅ Best for most applications
- ✅ Easy to maintain and refactor
- ❌ Less control than manual trait implementation
- ✅ Maximum flexibility and control
- ✅ Best for complex interface hierarchies
- ✅ Can optimize for specific use cases
- ❌ More boilerplate to maintain
- ✅ Ideal for plugin systems with hot-reload
- ✅ Supports actual WASM Component execution
- ✅ RON for Rust-friendly serialization
- ✅ Binary path for high-performance calls
- ✅ Bidirectional guest-host communication
- ❌ More complex than static approaches
- ❌ Requires WASM Components (not just tools)
- Start with the macro approach - It's the easiest way to get started
- Use traits for composition - Combine multiple interfaces using
CompositeWitInterface - Prefer native approaches - Use static API for known interfaces
- Use RON for dynamic calls - Rust-friendly types with runtime flexibility
- Use binary path for hot loops - When performance is critical
- Define clear interfaces - Well-designed WIT interfaces make your system more maintainable
- Test in isolation - Test handlers independently before integrating with WASM
-
🚀 Dynamic WASM Component Invocation
- Runtime guest export calls with
call_guest_raw_desc()(RON) - High-performance binary calls with
call_guest_binary() - Host import registration and invocation
- Runtime function discovery
- Runtime guest export calls with
-
🔤 RON Serialization Support
RonBindingandRonToolRegistryfor Rust-friendly types- Native Rust enum, tuple, option, and result support
typed_ron_tool!macro for type-safe tools
-
⚡ Dual Calling Paths
- RON path: Convenient, human-readable serialization
- Binary path: Zero-copy, canonical ABI performance
-
✅ Complete Type Support
- All basic types (Bool, Integers, Floats, String, Char)
- Complex types (List, Tuple, Record, Variant, Result, Option)
- Full nested type support (e.g.,
List<List<T>>,Option<Result<T, E>>)
-
📦 New Example:
wit-dynamic-advancedshowcasing all new features
See examples/README.md for migration guide and detailed examples.
Tairitsu (対立) carries a dual meaning:
- From Arcaea: Named after the character Tairitsu from the rhythm game Arcaea, representing the "Conflict" side
- Opposition & Duality: Reflects the architectural concept of this framework - the inherent duality and opposition between the WASM virtual machine (guest) and the host environment, connected through WIT (WebAssembly Interface Types)
See LICENSE for details.
