WeaveFFI generates type-safe bindings for 11 languages from a single IDL — no hand-written JNI, no duplicate implementations, no unsafe boilerplate. Define your API once in YAML, JSON, or TOML; 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.
1. Install the CLI:
cargo install weaveffi-cli2. Define your API in contacts.yml:
version: "0.3.0"
modules:
- name: contacts
structs:
- name: Contact
fields:
- name: id
type: i64
- name: name
type: string
- name: email
type: "string?"
functions:
- name: create_contact
params:
- name: name
type: string
- name: email
type: "string?"
return: Contact
- name: list_contacts
params: []
return: "[Contact]"3. Generate bindings:
weaveffi generate contacts.yml -o generated --target c,swift,python,node,dart4. Use the generated code from any of the eleven supported languages. Click each block below to see what WeaveFFI emits.
C — generated/c/weaveffi.h
typedef struct weaveffi_contacts_Contact weaveffi_contacts_Contact;
weaveffi_contacts_Contact* weaveffi_contacts_Contact_create(
int64_t id,
const char* name,
const char* email,
weaveffi_error* out_err);
void weaveffi_contacts_Contact_destroy(weaveffi_contacts_Contact* ptr);
const char* weaveffi_contacts_Contact_get_name(
const weaveffi_contacts_Contact* ptr);
weaveffi_contacts_Contact* weaveffi_contacts_create_contact(
const char* name,
const char* email,
weaveffi_error* out_err);
weaveffi_contacts_Contact** weaveffi_contacts_list_contacts(
size_t* out_len, weaveffi_error* out_err);Swift — generated/swift/Sources/WeaveFFI/WeaveFFI.swift
public class Contact {
let ptr: OpaquePointer
init(ptr: OpaquePointer) {
self.ptr = ptr
}
deinit {
weaveffi_contacts_Contact_destroy(ptr)
}
public var id: Int64 {
return weaveffi_contacts_Contact_get_id(ptr)
}
public var name: String {
let raw = weaveffi_contacts_Contact_get_name(ptr)
guard let raw = raw else { return "" }
defer { weaveffi_free_string(raw) }
return String(cString: raw)
}
public var email: String? { /* ... */ }
}
public enum Contacts {
public static func contacts_create_contact(_ name: String, _ email: String?) throws -> Contact { /* ... */ }
public static func contacts_list_contacts() throws -> [Contact] { /* ... */ }
}Python — generated/python/weaveffi/weaveffi.pyi
from typing import List, Optional
class Contact:
@property
def id(self) -> int: ...
@property
def name(self) -> str: ...
@property
def email(self) -> Optional[str]: ...
def contacts_create_contact(name: str, email: Optional[str]) -> "Contact": ...
def contacts_list_contacts() -> List["Contact"]: ...TypeScript — generated/node/types.d.ts
export interface Contact {
id: number;
name: string;
email: string | null;
}
export function contacts_create_contact(
name: string,
email: string | null,
): Contact;
export function contacts_list_contacts(): Contact[];Dart — generated/dart/lib/weaveffi.dart
class Contact {
final Pointer<Void> _handle;
Contact._(this._handle);
void dispose() { /* destroy native handle */ }
int get id { /* ... */ }
String get name { /* ... */ }
String? get email { /* ... */ }
}
Contact createContact(String name, String? email) { /* ... */ }
List<Contact> listContacts() { /* ... */ }- One IDL, eleven languages. Describe your API once 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. Works with any backend that can expose a C ABI: Rust (with first-class scaffolding via--scaffold), C, C++, or Zig. - Idiomatic per-target output. No lowest-common-denominator surface area.
Swift gets
async/awaitandthrows, Kotlin getssuspendand JNI glue, Python gets typed.pyistubs, TypeScript getsPromises, Dart getsdart:ffi— all from the same definition.
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.3.0| Command | Description |
|---|---|
weaveffi new <name> |
Scaffold a new project with a starter IDL and Cargo.toml |
weaveffi generate <file> -o <dir> |
Generate bindings; --target c,swift,... to subset, --scaffold to emit Rust FFI stubs, --config cfg.toml for generator options, --templates dir/ for custom Tera overrides, --dry-run to preview |
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> |
Extract an IDL from annotated Rust source (alternative to writing IDL by hand) |
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 upgrade <file> |
Migrate an older IDL to the current schema version; --check for CI |
weaveffi schema --format json-schema |
Print the JSON Schema for the IDL |
weaveffi schema-version |
Print the current IR schema version (0.3.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.3.0"
modules: ...Regenerate the schema with weaveffi schema --format json-schema > weaveffi.schema.json.
Full documentation lives at https://docs.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 a 1.0.0 release candidate. Every PRD-v4 phase is complete and
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, weaveffi diff --check
on every sample) passes. The C ABI naming convention
({c_prefix}_{module}_{function}), the weaveffi-abi runtime symbols
(weaveffi_free_string, weaveffi_free_bytes, weaveffi_error_clear),
and the IDL schema are now considered stable; any breaking change is gated
behind a weaveffi upgrade migration step so you can move IDLs forward
mechanically.
Remaining blockers before tagging 1.0.0:
weaveffi format --checkcurrently re-emits every IR field (includingnull,false, and[]defaults) when canonicalising an IDL, which makes the canonical form 2–3× longer than the hand-written sample IDLs insamples/*/. Either the formatter must learn to omitserde(default)values or every committed sample must be reformatted to the verbose canonical form. Tracked as a follow-up; does not block any CI gate today.
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.