Getting Started
This guide walks you through installing WeaveFFI, defining an API, generating multi-language bindings, implementing the Rust library, and calling it from C.
Prerequisites
You need the Rust toolchain (stable channel) installed. Verify with:
rustc --version
cargo --version
1) Install WeaveFFI
Install the CLI from crates.io:
cargo install weaveffi-cli
This puts the weaveffi binary on your PATH.
2) Create a new project
Scaffold a starter project:
weaveffi new my-project
cd my-project
This creates a my-project/ directory containing:
weaveffi.yml— an example API definition withadd,mul, andechofunctionsREADME.md— quick-start notes
3) Define your API
Open weaveffi.yml and replace its contents with an API that has a struct and
a function:
version: "0.1.0"
modules:
- name: math
structs:
- name: Point
fields:
- { name: x, type: f64 }
- { name: y, type: f64 }
functions:
- name: add
params:
- { name: a, type: i32 }
- { name: b, type: i32 }
return: i32
The IDL supports primitives (i32, f64, bool, string, bytes, handle),
optionals (string?), and lists ([i32]). See the
IDL Schema reference for the full specification.
4) Generate bindings
Run the generator to produce bindings for all targets:
weaveffi generate weaveffi.yml -o generated --scaffold
The --scaffold flag also emits a scaffold.rs with Rust FFI stubs you can
use as a starting point. The output tree looks like:
generated/
├── c/ # C header + convenience stubs
├── swift/ # SwiftPM package + Swift wrapper
├── android/ # Kotlin JNI wrapper + Gradle skeleton
├── node/ # N-API loader + TypeScript types
├── wasm/ # WASM loader stub
└── scaffold.rs # Rust FFI function stubs
5) Examine the generated output
C header (generated/c/weaveffi.h)
The C generator produces an opaque struct with lifecycle functions and getters,
plus a module-level function. Every exported function takes an out_err
parameter for error reporting:
typedef struct weaveffi_math_Point weaveffi_math_Point;
weaveffi_math_Point* weaveffi_math_Point_create(
double x, double y, weaveffi_error* out_err);
void weaveffi_math_Point_destroy(weaveffi_math_Point* ptr);
double weaveffi_math_Point_get_x(const weaveffi_math_Point* ptr);
double weaveffi_math_Point_get_y(const weaveffi_math_Point* ptr);
int32_t weaveffi_math_add(int32_t a, int32_t b, weaveffi_error* out_err);
Swift wrapper (generated/swift/Sources/WeaveFFI/WeaveFFI.swift)
Structs become classes that own an OpaquePointer and free it on deinit.
Module functions are grouped under a Swift enum namespace:
public class Point {
let ptr: OpaquePointer
deinit { weaveffi_math_Point_destroy(ptr) }
public var x: Double { weaveffi_math_Point_get_x(ptr) }
public var y: Double { weaveffi_math_Point_get_y(ptr) }
}
public enum Math {
public static func add(a: Int32, b: Int32) throws -> Int32 { ... }
}
TypeScript types (generated/node/types.d.ts)
Structs become interfaces with mapped types. Functions use the IR name directly (no module prefix):
export interface Point {
x: number;
y: number;
}
// module math
export function add(a: number, b: number): number
6) Implement the Rust library
The generated scaffold.rs contains todo!() stubs for every function.
Create a Rust library crate and fill in the implementations.
Cargo.toml:
[package]
name = "my-math"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
weaveffi-abi = { version = "0.1" }
src/lib.rs — implement the add function (struct lifecycle omitted for
brevity):
#![allow(unused)]
#![allow(unsafe_code)]
#![allow(clippy::not_unsafe_ptr_arg_deref)]
fn main() {
use std::os::raw::c_char;
use weaveffi_abi::{self as abi, weaveffi_error};
#[no_mangle]
pub extern "C" fn weaveffi_math_add(
a: i32,
b: i32,
out_err: *mut weaveffi_error,
) -> i32 {
abi::error_set_ok(out_err);
a + b
}
#[no_mangle]
pub extern "C" fn weaveffi_free_string(ptr: *const c_char) {
abi::free_string(ptr);
}
#[no_mangle]
pub extern "C" fn weaveffi_free_bytes(ptr: *mut u8, len: usize) {
abi::free_bytes(ptr, len);
}
#[no_mangle]
pub extern "C" fn weaveffi_error_clear(err: *mut weaveffi_error) {
abi::error_clear(err);
}
}
Key points:
- Every exported function uses
#[no_mangle]andextern "C". out_errmust always be cleared on success withabi::error_set_ok.- On error, call
abi::error_set(out_err, code, message)and return a zero/null value. - The library must export
weaveffi_free_string,weaveffi_free_bytes, andweaveffi_error_clearfor the runtime.
Build with:
cargo build
This produces a shared library (libmy_math.dylib on macOS,
libmy_math.so on Linux).
7) Build and test with C
Write a small C program that calls your library:
main.c:
#include <stdio.h>
#include "weaveffi.h"
int main(void) {
struct weaveffi_error err = {0};
int32_t sum = weaveffi_math_add(3, 4, &err);
if (err.code) {
printf("error: %s\n", err.message);
weaveffi_error_clear(&err);
return 1;
}
printf("add(3, 4) = %d\n", sum);
return 0;
}
Compile, link, and run:
# macOS
cc -I generated/c main.c -L target/debug -lmy_math -o my_example
DYLD_LIBRARY_PATH=target/debug ./my_example
# Linux
cc -I generated/c main.c -L target/debug -lmy_math -o my_example
LD_LIBRARY_PATH=target/debug ./my_example
Expected output:
add(3, 4) = 7
Next steps
- Run
weaveffi doctorto check which platform toolchains are available. - See the Calculator tutorial for a full end-to-end walkthrough including Swift and Node.js.
- Read the IDL Schema reference for all supported types and features.
- Explore the Generators section for target-specific details.