1.8. WASM component model - ffi.wit
The WebAssembly Component Model (bytecodealliance.org; Preview 2 stabilized in January 2024) is the foundation for defining typed cross-language interfaces. It replaces the raw pointer + length model that traditional C ABI FFI (1.7) relies on with rich types: record / variant / resource.
1.8.1. WIT - WebAssembly Interface Types
WIT is a language-neutral interface definition language.
package example:fs;
interface fs {
record stat {
size: u64,
mtime: u64,
is-dir: bool,
}
variant error {
not-found,
permission-denied,
io-error(string),
}
resource file {
constructor(path: string);
read: func(buf-size: u32) -> result<list<u8>, error>;
close: func();
}
open: func(path: string) -> result<file, error>;
}
An interface defined this way can be imported or exported from any language: Rust, Go, Python, rok and so on.
1.8.2. Binding form on the rok side
The import side uses an external component. Here wit/fs.wit holds the 1.8.1 interface with package example:fs; at its top.
ffi::wit::import { "wit/fs.wit" } // relative to this source file
use example::fs::fs::{File, Error};
let f = File::new("data.txt");
let bytes = try f.read(1024); // throws (Error) -> List<u8>
f.close();
The export side provides a component; calculator.wit declares add: func(a: s32, b: s32) -> s32 in a world.
ffi::wit::export { "calculator.wit" }
#[wit::export]
fn add(a: i32, b: i32) -> i32 { a + b }
Paths and placement
- A path is relative to the source file. It names a
.witfile or a package directory, whosedeps/holds its dependencies. - The item is allowed in the program's package and in dependencies. Its bindings go in the module that holds it (in a dependency, that dependency's module), under
ns::pkg::iface(wasi::filesystem::types). - WIT types become PascalCase rok types (
descriptor-type→DescriptorType,file→File); functions and fields become snake_case.
Worlds
A world in a named file is the component. Otherwise the keyword decides: ffi::wit::import imports the interfaces, ffi::wit::export exports them. include ... with { a as b } renames the included items.
Type mapping
| WIT | rok |
|---|---|
record | struct |
variant, enum | enum |
flags | struct of bool fields with Default (at most 32 flags) |
option<T> | T? (nested options keep their layers: option<option<T>> → T??) |
result<T, E> | Result<T, E>; a function returning it is throws (E) -> T |
tuple<..> | tuple |
list<T> | List<T> |
list<T, N> | [T; N] |
string | str (UTF-8) |
char, bool, f32, f64 | the same |
sN / uN | iN / uN |
future<T> | AsyncFuture<T> |
stream<T> | rok::core::channel::Receiver<T> (closing the sender ends it) |
error-context | rok::bare::ffi::wit::ErrorContext |
imported resource | a handle struct, dropped with using / Drop |
exported resource | the user's #[wit::export] pub struct |
Async
An async func is an async fn, both imported and exported. A synchronous task (a preview 2 main, a synchronous export) cannot wait for the component model; doing so is a runtime panic, so async imports are awaited inside async exports. A preview 3 command exports wasi:cli/[email protected] as #[wit::export] async fn run() -> Result<(), ()>. Async export calls run concurrently, each a component model task; a call returns its result in its own turn. Inside an async export, sockets and timers are waited on between the host's events; with component model events also pending they are checked at least every 10 ms (a preview 2 pollable and the component model's events cannot be waited on at once). An async export that waits for what nothing delivers waits; another call may still deliver it.
The async canonical ABI the generated bindings call lives in rok::std::wit: it runs on the executor, and stream<T> maps to a core type. Memory helpers, blocks, handles and ErrorContext stay in rok::bare::ffi::wit.
Exports
An exported function is a #[wit::export] fn in the item's module, matched to the WIT function by its snake_case name. An exported resource is a #[wit::export] pub struct whose impl gives new (the constructor), the methods and the static functions; a fallible constructor (result<r, ..>) is a throwing new, and the resource's destructor runs its Drop. A missing export is a compile error.
A package that exports WIT and whose entry file has neither fn main nor top-level statements is a reactor: it exports no wasi:cli/run (4.16.1).
Targets
WIT needs wasm32-wasi (a WASI preview 2 component); on any other target it is a compile error. Exports may be called before main: the runtime starts on the first export call.
A prebuilt dependency carries the .wit files its ffi::wit items read in its ROKHIR (refs-lang 9.4.7); a dependent does not read them from disk.
Restrictions
An imported interface cannot use types of an interface the component exports; that is a compile error.
1.8.3. Differences between C FFI and WIT
The two approaches differ clearly along several axes.
| Aspect | C FFI (1.7) | WIT (this chapter) |
|---|---|---|
| Type system | C primitives + user structs | record / variant / resource |
| Safety | low (raw ptrs managed by hand) | high (typed) |
| Memory management | malloc/free | resource (automatic destructor) |
| Error handling | errno / return value convention | first-class result<T, E> |
| String | null-terminated | length-prefixed UTF-8 |
| List | (ptr, len) manual | list<T> automatic |
| Call cost | almost none (direct call) | trampoline (~ns) |
| Scope | every native target | wasm32-wasi (preview 2 component) |
| unsafe required | ✓ | ✗ |
1.8.4. Selection criteria
The criteria are simple: they depend on what is being called and where it is deployed.
| Scenario | Recommendation |
|---|---|
| Calling a traditional C library | C FFI (1.7) |
| System calls (libc, Win32) | C FFI |
| GPU / SIMD intrinsics | C FFI |
| Safe interface with other languages | WIT |
| Deployment to WASM environments | WIT |
| Plugin / sandbox isolation | WIT |
| Multi-language (Modular / Mojo etc.) | WIT |
The main significance of this chapter is that rok supports both traditional C FFI and WIT. Users choose either according to their safety, performance and target environment constraints.
1.8.5. References
For further reading: