WeaveFFI
WeaveFFI is a Rust-first toolkit for generating cross-language FFI surfaces and language-specific templates from a concise input model. This book covers the concepts, setup, and end-to-end workflows.
- Goals: strong safety model, clear memory ownership, ergonomic bindings.
- Targets: C, Swift, Android (JNI), Node.js, and Web/WASM.
See the roadmap for high-level milestones and the getting started guide to try it.
WeaveFFI Roadmap (Rust core)
This roadmap tracks high-level goals for the first five releases, with a detailed step-by-step plan to reach 0.1.0. The project uses a Rust core that exposes an FFI-friendly, stable C ABI used by language-specific bindings.
Release goals
- 0.1.0 — MVP foundation: Rust workspace, IDL/IR v0, stable C ABI, basic type coverage, error and memory model, code generators for C header, Swift (SwiftPM System Library), Android (JNI skeleton), Node.js (TypeScript + ffi-napi), minimal Web/WASM target, CLI, samples, and docs.
- 0.2.0 — Type system + packaging: Structs/enums/options, arrays/slices, richer string/byte handling, first-class async surface (callbacks → futures), packaging improvements (SwiftPM/Gradle/npm templates), better cross-compilation UX.
- 0.3.0 — Annotated Rust input: Support reading an annotated Rust crate as input (derive/proc-macro), advanced async/concurrency (streams), improved diagnostics, and template customization hooks.
- 0.4.0 — Safety + performance: Zero-copy where safe, arena/pool patterns, lifetime-safe handles, incremental codegen, template plugins, caching, and DX polish across all targets. (High-level goals; detailed plan TBD.)
- 0.5.0 — Ecosystem expansion: Additional languages (e.g., Python, .NET), distribution story (prebuilt artifacts), stability hardening, and release automation. (High-level goals; detailed plan TBD.)
0.1.0 detailed plan (MVP)
Focus: Deliver a usable MVP that accepts a simple IDL/IR, generates a stable C ABI plus minimal bindings/templates for Swift, Android, Node.js, and Web/WASM, and ships a CLI with a working sample.
1) Workspace scaffolding
- Create a Rust workspace with crates:
weaveffi-ir: in-memory IR + (de)serialization viaserde(YAML/TOML/JSON)weaveffi-core: core logic (validation, codegen orchestration, templates)weaveffi-gen-c: C header generator (and helper C stubs if needed)weaveffi-gen-swift: SwiftPM System Library template + thin Swift wrapperweaveffi-gen-android: JNI glue + Gradle module template (Kotlin)weaveffi-gen-node: TypeScript template usingffi-napito load the .dylib/.soweaveffi-cli: end-user CLI (weaveffi) invoking core/generatorssamples/calculator: tiny Rust lib compiled to C ABI for end-to-end testing
2) IR/IDL v0 (input model)
- Define a minimal but practical schema to describe:
- Functions (name, doc, params, return, async=false for 0.1.0)
- Types: primitives (i32, u32, i64, f64, bool),
string(UTF-8),bytes, andhandle(opaque resource IDs); no nested structs for 0.1.0 - Errors: named error domain with numeric codes + messages
- Implement parsers via
serdefor YAML and/or TOML; emit helpful diagnostics. - Validate IR (name collisions, reserved keywords, unsupported shapes, etc.).
3) ABI, memory, and error model
- Establish a stable C ABI surface convention (prefix, naming, versioning):
weaveffi_<module>_<function>(... , weaveffi_error* out_err)style- All strings returned are UTF-8, owned by the Rust core; provide
weaveffi_free_string(const char*)andweaveffi_free_bytes(uint8_t*, size_t) - Opaque
handlerepresented asuint64_t(oruintptr_t) from user perspective
- Error model:
- A compact
weaveffi_errorstruct with{ code: int32_t, const char* message } - Map Rust
Result<T, E>to C: fillout_err->code != 0on error - Provide
weaveffi_error_clear(weaveffi_error*)to release message buffers
- A compact
4) Code generators (templates)
- C generator (
weaveffi-gen-c):- Emit a single
.hwith function prototypes, error types, free functions - Optionally emit a tiny
.cconvenience layer if helpful for some targets
- Emit a single
- Swift generator (
weaveffi-gen-swift):- SwiftPM System Library template with
module.modulemapreferencing the header - Thin Swift wrapper translating to ergonomic Swift types and throwing errors
- SwiftPM System Library template with
- Android generator (
weaveffi-gen-android):- Kotlin (or Java) JNI wrapper class + Gradle
android-librarytemplate - C JNI shims that forward to the C ABI; sample
CMakeLists.txt
- Kotlin (or Java) JNI wrapper class + Gradle
- Node generator (
weaveffi-gen-node):- TypeScript wrapper using
ffi-napito load the C ABI shared library at runtime - Generate
.d.tstypes from the IR; include basic build scripts
- TypeScript wrapper using
- Web/WASM minimal (
weaveffi-core):- Documented
wasm32-unknown-unknownbuild with a thin JS glue stub; full ergonomics can wait for 0.2.x/0.3.x
- Documented
5) CLI
weaveffi new <name>: create a starter layout (IDL + example module)weaveffi generate: read IDL → validate IR → emit C header + platform templatesweaveffi doctor: check for toolchain prerequisites (Rust, Xcode, Android NDK, Node toolchain), reporting actionable guidance
6) Sample: calculator
- Rust
samples/calculatorcrate exporting a few functions via the C ABI:add(i32, i32) -> i32,mul(i32, i32) -> i32,echo(string) -> string- One fallible function returning an error code/message
- Include ready-to-run generated outputs for each target in a
examples/folder.
7) Tooling and CI
- GitHub Actions workflow: build
weaveffi-clifor macOS and Linux; run unit tests - Basic integration test: generate bindings from the calculator IDL and compile the produced templates (at least header + Node wrapper) in CI
8) Documentation
READMEquickstart and link to this roadmap- Docs pages for: IDL schema, memory & error model, per-platform setup/run steps
- End-to-end tutorial using the calculator sample
9) Release and versioning
- Tag
v0.1.0; attach CLI binaries for macOS (arm64/x86_64) and Linux (x86_64) - Publish npm template (if applicable) as a starter, and provide SwiftPM/Gradle template repos or archives
10) Acceptance checklist
- CLI can read a calculator IDL, generate artifacts, and the artifacts compile
- C header compiles; Node wrapper can call into the shared library successfully
- SwiftPM System Library builds and links on macOS/iOS simulator locally
- Android template builds an
.aarwith JNI stubs (smoke test) - Docs explain memory management and error handling clearly
Getting Started
Prerequisites
- Rust stable toolchain and
cargo - mdBook for local docs
Install mdBook:
cargo install mdbook
Local docs preview
Run a live-reloading dev server:
mdbook serve docs -p 3000 -n 127.0.0.1
Open http://127.0.0.1:3000
Build full site locally (docs + Rust API)
# Build mdBook into ./site
mdbook build docs --dest-dir site
# Build Rust API docs
cargo doc --workspace --all-features --no-deps
# Assemble into the site
mkdir -p site/api/rust
cp -r target/doc/* site/api/rust/
# Prevent Jekyll processing on GitHub Pages
touch site/.nojekyll
Open site/index.html in your browser.
CI/CD
A GitHub Actions workflow publishes the site to GitHub Pages on pushes to main.
Samples
This repo includes sample projects to showcase end-to-end usage.
Calculator (Rust crate)
Path: samples/calculator
Build:
cargo build -p calculator
The examples directory contains pre-generated outputs per target under generated/ and runnable examples under examples/.
Reference
IDL Schema
WeaveFFI consumes a concise, serializable input model (IDL/IR) that describes modules, functions, parameters, return types, and optional error domains. YAML, JSON, and TOML are supported; YAML examples are shown here.
Top-level structure
- version: string (e.g., "0.1.0")
- modules: array of modules
Module:
- name: string (lowercase recommended)
- functions: array of functions
- errors: optional error domain { name, codes[] }
Function:
- name: string
- params: array of { name, type }
- return: optional type
- doc: optional string
- async: boolean (present for forward-compat; not supported in 0.1.0)
Types (primitive set for 0.1.0): i32, u32, i64, f64, bool, string (UTF-8), bytes, handle (opaque 64-bit id)
Example (calculator)
version: "0.1.0"
modules:
- name: calculator
functions:
- name: add
params:
- { name: a, type: i32 }
- { name: b, type: i32 }
return: i32
- name: mul
params:
- { name: a, type: i32 }
- { name: b, type: i32 }
return: i32
- name: div
params:
- { name: a, type: i32 }
- { name: b, type: i32 }
return: i32
- name: echo
params:
- { name: s, type: string }
return: string
Validation rules
- Module, function, and parameter names must be unique within their scopes.
- Reserved keywords are rejected (e.g.,
async,fn,struct, etc.). asyncfunctions are not supported in 0.1.0 and will fail validation.- Error domain names must not collide with function names.
ABI mapping (0.1.0)
- Parameters map to C ABI types;
stringandbytesare passed as pointer + length. - Return values are direct scalars except:
string: returnsconst char*allocated by Rust; caller must free viaweaveffi_free_string.bytes: returnsconst uint8_t*and requires an extrasize_t* out_lenparam; caller frees withweaveffi_free_bytes.
- Each function takes a trailing
weaveffi_error* out_errfor error reporting.
Error domain (forward-looking)
You can declare an optional error domain on a module to reserve symbolic names and numeric codes. 0.1.0 validates domains for uniqueness and non-zero codes; future versions will wire these codes through generators for richer error typing.
Memory and Error Model
This section summarizes the C ABI conventions exposed by WeaveFFI and how to manage ownership across the FFI boundary.
Error handling
- Every generated C function ends with an
out_errparameter of typeweaveffi_error*. - On success:
out_err->code == 0andout_err->message == NULL. - On failure:
out_err->code != 0andout_err->messagepoints to a Rust-allocated NUL-terminated UTF-8 string that must be cleared.
Relevant declarations (from the generated header):
typedef struct weaveffi_error { int32_t code; const char* message; } weaveffi_error;
void weaveffi_error_clear(weaveffi_error* err);
Typical C usage:
struct weaveffi_error err = {0};
int32_t sum = weaveffi_calculator_add(3, 4, &err);
if (err.code) { fprintf(stderr, "%s\n", err.message ? err.message : ""); weaveffi_error_clear(&err); }
Notes:
- The default unspecified error code used by the runtime is
-1. - Future versions may map module error domains to well-known codes.
Strings and bytes
Returned strings are owned by Rust and must be freed by the caller:
const char* s = weaveffi_calculator_echo((const uint8_t*)msg, strlen(msg), &err);
// ... use s ...
weaveffi_free_string(s);
Returned bytes include a separate out-length parameter and must be freed by the caller:
size_t out_len = 0;
const uint8_t* buf = weaveffi_module_fn(/* params ... */, &out_len, &err);
// ... copy data from buf ...
weaveffi_free_bytes((uint8_t*)buf, out_len);
Relevant declarations:
void weaveffi_free_string(const char* ptr);
void weaveffi_free_bytes(uint8_t* ptr, size_t len);
Handles
Opaque resources are represented as weaveffi_handle_t (64-bit). Treat them as
tokens; their lifecycle APIs are defined by your module.
Language wrappers
- Swift: the generated wrapper throws
WeaveFFIErrorand automatically clears errors and frees returned strings. - Node: the provided N-API addon clears errors and frees returned strings; the generated
JS loader expects a compiled addon
index.nodeplaced next to it.
C-string safety
When constructing C strings, interior NUL bytes are sanitized on the Rust side to maintain valid C semantics.
Naming and Package Conventions
This guide standardizes how we name the Weave projects, repositories, packages, modules, and identifiers across ecosystems.
Human-facing brand names (prose)
- Use condensed names in sentences and documentation:
- WeaveFFI
- WeaveHeap
Repository and package slugs (URLs and registries)
-
Use condensed lowercase slugs for top-level repositories and published packages:
- GitHub:
weaveffi,weaveheap(repos:weavefoundry/weaveffi,weavefoundry/weaveheap) - crates.io (top-level crates):
weaveffi,weaveheap - npm:
@weavefoundry/weaveffi,@weavefoundry/weaveheap - PyPI:
weaveffi,weaveheap - SPM (repo slug):
weaveffi,weaveheap
- GitHub:
-
Use hyphenated slugs for subpackages and components, prefixed with the top-level slug:
- Examples:
weaveffi-core,weaveffi-ir,weaveheap-core
- Examples:
Rationale: condensed top-level slugs unify handles across registries and are ergonomic to type; hyphenated subpackages remain idiomatic and map cleanly to ecosystems that normalize to underscores or CamelCase.
Code identifiers by ecosystem
-
Rust
- Crates: hyphenated subcrates on crates.io (e.g.,
weaveffi-core), imported as underscores (e.g.,weaveffi_core). Top-level crate (if any):weaveffi. - Modules/paths: snake_case.
- Types/traits/enums: CamelCase (e.g.,
WeaveFFI).
- Crates: hyphenated subcrates on crates.io (e.g.,
-
Swift / Apple platforms
- Package products and modules: UpperCamelCase (e.g.,
WeaveFFI,WeaveHeap). - Keep repo slug condensed; SPM product name provides the CamelCase surface.
- Package products and modules: UpperCamelCase (e.g.,
-
Java / Kotlin (Android)
- Group ID / package base: reverse-DNS, all lowercase (e.g.,
com.weavefoundry.weaveffi). - Artifact ID: top-level condensed (e.g.,
weaveffi); sub-artifacts hyphenated (e.g.,weaveffi-android). - Class names: UpperCamelCase (e.g.,
WeaveFFI).
- Group ID / package base: reverse-DNS, all lowercase (e.g.,
-
JavaScript / TypeScript (Node, bundlers)
- Package name: scope + condensed for top-level, hyphenated for subpackages (e.g.,
@weavefoundry/weaveffi,@weavefoundry/weaveffi-core). - Import alias: flexible, prefer
WeaveFFIin examples when using default exports or named namespaces.
- Package name: scope + condensed for top-level, hyphenated for subpackages (e.g.,
-
Python
- PyPI name: top-level condensed (e.g.,
weaveffi); subpackages hyphenated (e.g.,weaveffi-core). - Import module: condensed for top-level (e.g.,
import weaveffi); underscores for hyphenated subpackages (e.g.,import weaveffi_core).
- PyPI name: top-level condensed (e.g.,
-
C / CMake
- Target/library names: snake_case (e.g.,
weaveffi,weaveffi_core). - Header guards / include dirs: snake_case or directory-based (e.g.,
#include <weaveffi/weaveffi.h>).
- Target/library names: snake_case (e.g.,
Writing guidelines
- In prose, prefer the condensed brand names: “WeaveFFI”, “WeaveHeap”.
- In code snippets, follow the host language conventions above.
- For cross-language docs, show both the repo/package slug and the language-appropriate identifier on first mention, e.g., “Install
weaveffi(import asweaveffi, Swift moduleWeaveFFI). For subpackages, installweaveffi-core(import asweaveffi_core).”
Migration guidance
- Rename existing repositories to
weaveffiandweaveheap(GitHub will auto-redirect; update docs/badges). - New crates and packages should follow the condensed top-level + hyphenated subpackage pattern:
- Rust crates:
weaveffi-*,weaveheap-*. - npm packages:
@weavefoundry/weaveffi-*,@weavefoundry/weaveheap-*. - Swift products: UpperCamelCase (e.g.,
WeaveFFICore).
- Rust crates:
- Prefer condensed top-level slugs. Avoid hyphenated top-level slugs like
weave-ffi,weave-heapgoing forward.
Examples
-
Rust
- Crate:
weaveffi-core - Import:
use weaveffi_core::{WeaveFFI};
- Crate:
-
Swift (SPM)
- Repo:
weaveffi - Package product:
WeaveFFI - Import:
import WeaveFFI
- Repo:
-
Python
- Package:
weaveffi - Import:
import weaveffi as ffi
- Package:
-
Node
- Package:
@weavefoundry/weaveffi - Import:
import { WeaveFFI } from '@weavefoundry/weaveffi'
- Package:
Generators
This section contains language-specific generators and guidance for using the artifacts they produce. Choose a target below to explore the details.
Android
The Android generator produces a Gradle android-library template with:
- Kotlin wrapper
WeaveFFIthat declaresexternal funs - JNI C shims that call into the generated C ABI
CMakeLists.txtfor building the shared library
Generated artifacts
generated/android/settings.gradlegenerated/android/build.gradlegenerated/android/src/main/java/com/weaveffi/WeaveFFI.ktgenerated/android/src/main/cpp/{weaveffi_jni.c,CMakeLists.txt}
Build steps
- Ensure Android SDK and NDK are installed (Android Studio recommended).
- Open
generated/androidin Android Studio. - Sync Gradle and build the
:weaveffiAAR. - Integrate the AAR into your app module. Ensure your app loads the Rust-produced
native library (e.g.,
libcalculator) at runtime on device/emulator.
The JNI shims convert strings/bytes and propagate errors by throwing RuntimeException.
C
The C generator emits a single header weaveffi.h containing function prototypes,
error types, and memory helpers; it also includes an optional weaveffi.c placeholder
for future convenience wrappers.
Generated artifacts
generated/c/weaveffi.hgenerated/c/weaveffi.c
Key declarations:
typedef struct weaveffi_error { int32_t code; const char* message; } weaveffi_error;
void weaveffi_error_clear(weaveffi_error* err);
void weaveffi_free_string(const char* ptr);
void weaveffi_free_bytes(uint8_t* ptr, size_t len);
Build and run (calculator sample)
# Build the Rust sample library (produces libcalculator)
cargo build -p calculator
cd examples/c
cc -I ../../generated/c main.c -L ../../target/debug -lcalculator -o c_example
DYLD_LIBRARY_PATH=../../target/debug ./c_example
# On Linux, use LD_LIBRARY_PATH
See examples/c/main.c for usage of errors and returned strings.
Node
The Node generator produces a small CommonJS loader and .d.ts types describing
your functions. For the examples in this repo, a separate N-API addon crate
(weaveffi-node-addon) loads the C ABI symbols and exposes JS-friendly functions.
Generated artifacts
generated/node/index.js– CommonJS loader that requires./index.nodegenerated/node/types.d.ts– function signatures inferred from your IDLgenerated/node/package.json
Running the example
# Build the Rust libraries
cargo build -p calculator
cargo build -p weaveffi-node-addon
# Place the addon where the loader expects it
cp target/debug/libindex.dylib generated/node/index.node
# Run the example
cd examples/node
DYLD_LIBRARY_PATH=../../target/debug npm start
Notes:
- On Linux, use
LD_LIBRARY_PATHinstead ofDYLD_LIBRARY_PATH. - The loader expects the compiled addon next to it as
index.node.
Swift
The Swift generator emits a SwiftPM System Library (CWeaveFFI) that references
the generated C header and a thin Swift module (WeaveFFI) that wraps the C API
with Swift types and throws-based error handling.
Generated artifacts
generated/swift/Package.swiftgenerated/swift/WeaveFFI/module.modulemap– points at../c/weaveffi.hgenerated/swift/Sources/WeaveFFI/WeaveFFI.swift– thin wrapper
Try the example app
# Build the Rust sample
cargo build -p calculator
cd examples/swift
swiftc \
-I ../../generated/swift/Sources/CWeaveFFI \
-L ../../target/debug -lcalculator \
-Xlinker -rpath -Xlinker ../../target/debug \
Sources/App/main.swift -o .build/debug/App
DYLD_LIBRARY_PATH=../../target/debug .build/debug/App
Integration via SwiftPM in a real app can be done by adding the System Library and linking it with your target; see the module map for header linkage and name.
WASM
The WASM generator produces a minimal JS loader and README to help get started
with wasm32-unknown-unknown. Full ergonomics are planned for future releases.
Generated artifacts
generated/wasm/weaveffi_wasm.jsgenerated/wasm/README.md
Build
rustup target add wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown --release
Serve the .wasm and load it with the provided JS helper.
API
Reference documentation and external API links.
- Rust API is generated via
cargo docand published under the link below.
Rust API (cargo doc)
Tutorials
Tutorial: Calculator end-to-end
This tutorial uses the included samples/calculator crate and shows how to generate
artifacts and run platform examples.
1) Generate artifacts
weaveffi generate samples/calculator/calculator.yml -o generated
This writes headers and templates under generated/:
generated/c: C header and convenience C filegenerated/swift: SwiftPM System Library and thin Swift wrappergenerated/android: Kotlin wrapper + JNI shims + Gradle skeletongenerated/node: JS loader +.d.tsgenerated/wasm: minimal loader stub
2) Build the Rust sample
cargo build -p calculator
3) Run the C example
cd examples/c
cc -I ../../generated/c main.c -L ../../target/debug -lcalculator -o c_example
DYLD_LIBRARY_PATH=../../target/debug ./c_example
4) Run the Node example
cargo build -p weaveffi-node-addon
cp target/debug/libindex.dylib generated/node/index.node
cd examples/node
DYLD_LIBRARY_PATH=../../target/debug npm start
5) Try Swift (optional)
cargo build -p calculator
cd examples/swift
swiftc \
-I ../../generated/swift/Sources/CWeaveFFI \
-L ../../target/debug -lcalculator \
-Xlinker -rpath -Xlinker ../../target/debug \
Sources/App/main.swift -o .build/debug/App
DYLD_LIBRARY_PATH=../../target/debug .build/debug/App
6) Android and WASM
- Open
generated/androidin Android Studio and build the:weaveffiAAR. - Build for WASM:
cargo build --target wasm32-unknown-unknown --releaseand load withgenerated/wasm/weaveffi_wasm.js.