Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 via serde (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 wrapper
    • weaveffi-gen-android: JNI glue + Gradle module template (Kotlin)
    • weaveffi-gen-node: TypeScript template using ffi-napi to load the .dylib/.so
    • weaveffi-cli: end-user CLI (weaveffi) invoking core/generators
    • samples/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, and handle (opaque resource IDs); no nested structs for 0.1.0
    • Errors: named error domain with numeric codes + messages
  • Implement parsers via serde for 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*) and weaveffi_free_bytes(uint8_t*, size_t)
    • Opaque handle represented as uint64_t (or uintptr_t) from user perspective
  • Error model:
    • A compact weaveffi_error struct with { code: int32_t, const char* message }
    • Map Rust Result<T, E> to C: fill out_err->code != 0 on error
    • Provide weaveffi_error_clear(weaveffi_error*) to release message buffers

4) Code generators (templates)

  • C generator (weaveffi-gen-c):
    • Emit a single .h with function prototypes, error types, free functions
    • Optionally emit a tiny .c convenience layer if helpful for some targets
  • Swift generator (weaveffi-gen-swift):
    • SwiftPM System Library template with module.modulemap referencing the header
    • Thin Swift wrapper translating to ergonomic Swift types and throwing errors
  • Android generator (weaveffi-gen-android):
    • Kotlin (or Java) JNI wrapper class + Gradle android-library template
    • C JNI shims that forward to the C ABI; sample CMakeLists.txt
  • Node generator (weaveffi-gen-node):
    • TypeScript wrapper using ffi-napi to load the C ABI shared library at runtime
    • Generate .d.ts types from the IR; include basic build scripts
  • Web/WASM minimal (weaveffi-core):
    • Documented wasm32-unknown-unknown build with a thin JS glue stub; full ergonomics can wait for 0.2.x/0.3.x

5) CLI

  • weaveffi new <name>: create a starter layout (IDL + example module)
  • weaveffi generate: read IDL → validate IR → emit C header + platform templates
  • weaveffi doctor: check for toolchain prerequisites (Rust, Xcode, Android NDK, Node toolchain), reporting actionable guidance

6) Sample: calculator

  • Rust samples/calculator crate 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-cli for 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

  • README quickstart 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 .aar with 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.).
  • async functions 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; string and bytes are passed as pointer + length.
  • Return values are direct scalars except:
    • string: returns const char* allocated by Rust; caller must free via weaveffi_free_string.
    • bytes: returns const uint8_t* and requires an extra size_t* out_len param; caller frees with weaveffi_free_bytes.
  • Each function takes a trailing weaveffi_error* out_err for 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_err parameter of type weaveffi_error*.
  • On success: out_err->code == 0 and out_err->message == NULL.
  • On failure: out_err->code != 0 and out_err->message points 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 WeaveFFIError and 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.node placed 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
  • Use hyphenated slugs for subpackages and components, prefixed with the top-level slug:

    • Examples: weaveffi-core, weaveffi-ir, weaveheap-core

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).
  • Swift / Apple platforms

    • Package products and modules: UpperCamelCase (e.g., WeaveFFI, WeaveHeap).
    • Keep repo slug condensed; SPM product name provides the CamelCase surface.
  • 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).
  • 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 WeaveFFI in examples when using default exports or named namespaces.
  • 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).
  • 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>).

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 as weaveffi, Swift module WeaveFFI). For subpackages, install weaveffi-core (import as weaveffi_core).”

Migration guidance

  • Rename existing repositories to weaveffi and weaveheap (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).
  • Prefer condensed top-level slugs. Avoid hyphenated top-level slugs like weave-ffi, weave-heap going forward.

Examples

  • Rust

    • Crate: weaveffi-core
    • Import: use weaveffi_core::{WeaveFFI};
  • Swift (SPM)

    • Repo: weaveffi
    • Package product: WeaveFFI
    • Import: import WeaveFFI
  • Python

    • Package: weaveffi
    • Import: import weaveffi as ffi
  • Node

    • Package: @weavefoundry/weaveffi
    • Import: import { WeaveFFI } from '@weavefoundry/weaveffi'

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 WeaveFFI that declares external funs
  • JNI C shims that call into the generated C ABI
  • CMakeLists.txt for building the shared library

Generated artifacts

  • generated/android/settings.gradle
  • generated/android/build.gradle
  • generated/android/src/main/java/com/weaveffi/WeaveFFI.kt
  • generated/android/src/main/cpp/{weaveffi_jni.c,CMakeLists.txt}

Build steps

  1. Ensure Android SDK and NDK are installed (Android Studio recommended).
  2. Open generated/android in Android Studio.
  3. Sync Gradle and build the :weaveffi AAR.
  4. 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.h
  • generated/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.node
  • generated/node/types.d.ts – function signatures inferred from your IDL
  • generated/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_PATH instead of DYLD_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.swift
  • generated/swift/WeaveFFI/module.modulemap – points at ../c/weaveffi.h
  • generated/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.js
  • generated/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 doc and 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 file
  • generated/swift: SwiftPM System Library and thin Swift wrapper
  • generated/android: Kotlin wrapper + JNI shims + Gradle skeleton
  • generated/node: JS loader + .d.ts
  • generated/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/android in Android Studio and build the :weaveffi AAR.
  • Build for WASM: cargo build --target wasm32-unknown-unknown --release and load with generated/wasm/weaveffi_wasm.js.