WeaveFFI generates type-safe bindings for 11 languages for any native library
that exposes a C ABI, whether it's written in Rust, C, C++, Zig, or anything
else: no hand-written JNI, no duplicate implementations, no unsafe boilerplate.
Define your API once as an IDL in YAML, JSON, or TOML and ship idiomatic
packages for C, C++, Swift, Kotlin/Android, Node.js, WebAssembly, Python, .NET,
Dart, Go, and Ruby that all talk to the same stable C ABI. Interfaces become
real classes with methods and automatic cleanup, and error domains become
typed errors consumers can catch and match on, not flat functions and raw
integer codes. Writing your producer in Rust? Annotate a normal module with
#[weaveffi::module] and the macro generates both the C ABI and the IDL for
you. Every path shares one engine, so the library you build and the bindings
you ship cannot drift.
1. Install the CLI:
cargo install weaveffi-cli2. Define your API as an IDL in kvstore.yml. Any native library that
exposes a C ABI (written in C, C++, Zig, Rust, ...) implements the symbols it
declares. An interface is a real object with methods; an error domain plus
throws: true gives its fallible members typed errors:
version: "0.5.0"
modules:
- name: kv
errors:
name: KvError
codes:
- { name: KeyNotFound, code: 1001, message: "key not found" }
- { name: StoreFull, code: 1003, message: "store has reached capacity" }
interfaces:
- name: Store
constructors:
- name: open
params:
- { name: path, type: string }
throws: true
methods:
- name: put
params:
- { name: key, type: string }
- { name: value, type: bytes }
return: bool
throws: true
- name: count
params: []
return: i64Producing in Rust? Skip the hand-written IDL: annotate a normal module with
#[weaveffi::module] (after cargo add weaveffi) and the macro emits the C ABI
and derives the IDL for you, so you write no unsafe glue. See
The Rust Producer Macro for the full
walkthrough.
#[weaveffi::module]
pub mod kv {
use std::collections::BTreeMap;
use std::sync::Mutex;
#[weaveffi::error]
#[derive(Debug)]
pub enum KvError {
/// key not found
KeyNotFound = 1001,
/// store has reached capacity
StoreFull = 1003,
}
#[weaveffi::interface]
pub struct Store {
entries: Mutex<BTreeMap<String, Vec<u8>>>,
}
impl Store {
pub fn open(path: String) -> Result<Store, KvError> {
let _ = path; // in-memory demo
Ok(Store { entries: Mutex::new(BTreeMap::new()) })
}
pub fn put(&self, key: String, value: Vec<u8>) -> Result<bool, KvError> {
let mut entries = self.entries.lock().unwrap();
if entries.len() >= 1024 && !entries.contains_key(&key) {
return Err(KvError::StoreFull);
}
Ok(entries.insert(key, value).is_none())
}
pub fn count(&self) -> i64 {
self.entries.lock().unwrap().len() as i64
}
}
}
// Emit the fixed C ABI runtime surface once per cdylib.
weaveffi::export_runtime!();3. Generate bindings from the IDL (or, for a Rust producer, straight from the annotated source):
weaveffi generate kvstore.yml -o generated --target c,swift,python
# Rust producer: point generate at the annotated source instead
weaveffi generate src/lib.rs -o generated --target c,swift,python4. Use the generated code from any of the eleven supported languages.
Every target gets a real Store class whose objects clean up after
themselves, and a typed KvError consumers can catch and match on. Click
each block below to see what WeaveFFI emits.
C: generated/c/weaveffi.h
typedef struct weaveffi_kv_Store weaveffi_kv_Store;
typedef enum {
weaveffi_kv_KvError_KeyNotFound = 1001,
weaveffi_kv_KvError_StoreFull = 1003
} weaveffi_kv_KvError;
weaveffi_kv_Store* weaveffi_kv_Store_open(
const char* path, weaveffi_error* out_err);
bool weaveffi_kv_Store_put(
const weaveffi_kv_Store* self,
const char* key,
const uint8_t* value_ptr, size_t value_len,
weaveffi_error* out_err);
int64_t weaveffi_kv_Store_count(
const weaveffi_kv_Store* self, weaveffi_error* out_err);
void weaveffi_kv_Store_destroy(weaveffi_kv_Store* self);Swift: generated/swift/Sources/WeaveFFI/WeaveFFI.swift
public enum KvError: Error, LocalizedError {
case keyNotFound(message: String)
case storeFull(message: String)
}
public final class Store {
// The native object is released automatically on deinit.
public static func open(path: String) throws -> Store { /* ... */ }
public func put(key: String, value: Data) throws -> Bool { /* ... */ }
public func count() -> Int64 { /* ... */ }
}Python: generated/python/weaveffi/weaveffi.pyi
class KvError(WeaveFFIError): ...
class KeyNotFound(KvError):
CODE: int # 1001
class StoreFull(KvError):
CODE: int # 1003
class Store:
@classmethod
def open(cls, path: str) -> "Store": ...
def put(self, key: str, value: bytes) -> bool: ...
def count(self) -> int: ...The remaining targets follow the same pattern with their own idioms: an owned class (or the closest analogue) wired to the object's destructor, and the module's error domain as a typed error or exception.
- One definition, eleven languages. Write the API once (safe Rust or an IDL) and ship packages to npm, SwiftPM, Maven, PyPI, NuGet, pub.dev, RubyGems, and Go modules. Each package is standalone: consumers don't need WeaveFFI installed.
- Stable C ABI underneath. Every target speaks to the same
extern "C"contract, so adding a new platform later is a code-gen change, not a rewrite. Rust producers get that C ABI for free from the#[weaveffi::module]macro; any other backend that can expose a C ABI (C, C++, Zig) implements the generated header directly. - Idiomatic per-target output. No lowest-common-denominator surface area.
Interfaces become real classes with methods and automatic disposal, and
error domains become typed errors (a Swift error enum, Python exception
classes, and each remaining target's own exception idiom). Swift gets
async/awaitandthrows, Kotlin getssuspendand JNI glue, Python gets typed.pyistubs, TypeScript getsPromises, and Dart getsdart:ffi, all from the same definition. - The whole IDL surface, on every target. Interfaces, typed error
domains, async functions, iterators, callbacks, and event listeners work
across all eleven languages (Wasm excepts callbacks/listeners and says so
loudly). Generators declare their capabilities and
weaveffi generatefails with a clear error (never a silent skip) if a target can't deliver a feature you use. See the feature matrix.
See Comparison for a side-by-side feature matrix versus UniFFI, cbindgen, diplomat, SWIG, and autocxx, plus an honest "when to choose WeaveFFI" guide.
| Target | Output directory | What you get |
|---|---|---|
| C | c/ |
weaveffi.h header with struct typedefs, function prototypes, and the shared weaveffi_error type |
| C++ | cpp/ |
RAII header (weaveffi.hpp) with move semantics, std::optional/std::vector/std::unordered_map wrappers, exception-based errors, and a CMakeLists.txt |
| Swift | swift/ |
SwiftPM package wrapping the C ABI with throws, async/await, and Codable-friendly types |
| Android | android/ |
Kotlin JNI wrapper, C shim, and a Gradle project skeleton |
| Node.js | node/ |
N-API addon loader + TypeScript declarations and a package.json |
| Wasm | wasm/ |
JavaScript loader + TypeScript declarations for wasm32-unknown-unknown builds |
| Python | python/ |
ctypes bindings + .pyi type stubs + pyproject.toml |
| .NET | dotnet/ |
C# P/Invoke bindings + .csproj + .nuspec for NuGet |
| Dart | dart/ |
dart:ffi bindings + pubspec.yaml for Flutter and Dart projects |
| Go | go/ |
CGo bindings + go.mod for Go modules |
| Ruby | ruby/ |
FFI gem bindings + .gemspec for RubyGems |
From crates.io (requires the Rust toolchain):
cargo install weaveffi-cliPre-built binaries for macOS, Linux, and Windows are attached to every
GitHub release. Download
the archive for your platform, extract the weaveffi binary, and put it on
your PATH.
Verify the install:
weaveffi --version
weaveffi schema-version # prints 0.5.0| Command | Description |
|---|---|
weaveffi new <name> |
Scaffold a new project with a starter IDL and Cargo.toml |
weaveffi generate <file> -o <dir> |
Generate bindings from annotated Rust (.rs) or an IDL (.yml/.json/.toml); --target c,swift,... to subset, --config cfg.toml for options, --scaffold to emit Rust FFI stubs (for non-macro producers), --dry-run to preview |
weaveffi package <file> -o <dir> |
Assemble publishable, per-platform packages that bundle a prebuilt native library; --binaries <dir> for prebuilt libs or --build <crate> to cross-compile a Rust producer |
weaveffi validate <file> |
Validate an IDL definition without generating; --format json for machine-readable output |
weaveffi lint <file> |
Lint an IDL and report non-fatal warnings |
weaveffi diff <file> |
Show what would change if bindings were regenerated; --check for CI |
weaveffi extract <file.rs> |
Derive an IDL from #[weaveffi::module]-annotated Rust source |
weaveffi format <file> |
Rewrite an IDL file in canonical form (sorted keys); --check for CI |
weaveffi watch <file> |
Re-run generate whenever the IDL file changes |
weaveffi schema --format json-schema |
Print the JSON Schema for the IDL |
weaveffi schema-version |
Print the current IR schema version (0.5.0) |
weaveffi doctor |
Check for required toolchains; --target swift to scope to one language, --format json for CI |
weaveffi completions <shell> |
Print shell completion scripts (bash, zsh, fish, powershell, elvish) |
Reference the JSON Schema from your IDL for editor autocompletion:
# yaml-language-server: $schema=./weaveffi.schema.json
version: "0.5.0"
modules: ...Regenerate the schema with weaveffi schema --format json-schema > weaveffi.schema.json.
Full documentation lives at https://weaveffi.com/ (sources under
docs/). Key pages:
- Introduction: what WeaveFFI is and why it exists
- Getting Started: install → IDL → generate → call from C
- Comparison: feature matrix vs UniFFI, cbindgen, diplomat, SWIG, autocxx
- FAQ: top questions about scope, runtime cost, and platform support
- Generators: per-target reference for each of the eleven languages
- Guides: memory ownership, error handling, async, configuration
WeaveFFI is in active 0.x development. Following Semantic
Versioning, the public surface (the CLI, the IDL
schema, the generated code, and the weaveffi-abi runtime symbols) may
change between minor releases while the project is pre-1.0, and only the
current IDL schema version is accepted. See Stability and
Versioning for what that means in practice and the
recommended weaveffi diff --check CI workflow.
The full quality gate (cargo fmt, cargo clippy -D warnings, cargo test, cargo doc -D warnings, cargo deny, cargo audit, cargo machete, cargo insta test --check, cargo bench --no-run, and
weaveffi diff --check on every sample) runs in CI on every PR.
Releases are fully automated by semantic-release
on merge to main.
See CONTRIBUTING.md for the development workflow, snapshot test conventions, fuzzing setup, and Conventional Commit rules.
Licensed under either of Apache License, Version 2.0 or MIT License at your option.