Stability and Versioning
WeaveFFI will follow Semantic Versioning from 1.0.0. Until then it’s in active pre-1.0 development and any surface may change between minor releases. This page says what the version numbers mean, what 1.0 will promise, how to keep a downstream project steady today, and how to migrate to the current release.
Three version numbers
- The crate version (
0.24.x) is shared by every published crate (weaveffi,weaveffi-macros,weaveffi-model,weaveffi-cli) and by the CLI. Releases are cut from Conventional Commits by release-plz. - The IDL schema version (
0.11.0) is theversion:an IDL document declares. Pre-1.0, only the current version is accepted; a document declaring any other is rejected withUnsupportedSchemaVersion. - The C ABI revision (
4) is what{prefix}_abi_version()returns. It changes only when the runtime surface or a calling convention changes incompatibly, independently of the other two.
What 1.0 will cover
After 1.0.0, these surfaces change incompatibly only in a major release:
- The CLI: subcommands, flags, exit codes, and documented output
formats (
validate --format jsonin particular). - The IDL schema: accepted keys, type syntax, and the exported JSON Schema.
- Generated code: exported symbols, signatures, type names, and package layouts. A patch release won’t change generated bytes; a minor release may add symbols but won’t remove or rename any.
- The C ABI revision and the contract it names.
- The public Rust API of the published crates.
Deprecation will precede removal: a deprecated feature warns for at least
one minor release (the CLI prints a diagnostic, and generators emit each
language’s deprecation marker from the IDL’s deprecated: field) and is
removed in the next major release with a migration note. Post-1.0 schema
bumps will ship with a migration tool and accept more than one version.
Determinism
Regenerating with the same WeaveFFI version from the same definition and
weaveffi.toml produces byte-identical output on every platform. Generators
never iterate hash maps into output, and a test regenerates the kitchen-sink
fixture twice and compares. Non-determinism is a bug; please report it with
the definition that triggers it.
CI workflow
Pin the CLI version and commit the generated output, so an upgrade is an
explicit, reviewable change. Then gate CI on weaveffi diff --check, which
regenerates in memory and compares with the committed directory without
writing anything:
- name: Bindings are up to date
run: |
cargo install weaveffi-cli --locked --version =0.24.0
weaveffi validate --warn
weaveffi diff --check
diff --check exits 0 when the output matches, 2 when files would
change, and 3 when files would be added or removed. For a Rust producer,
both commands build the crate first (see Library Mode),
so the job needs the Rust toolchain; pass --library <path> to reuse a
library an earlier step built.
Migrating to schema 0.11 and ABI 4
This release makes names global, replaces the per-module checksums with contract tables, lets callback interfaces return any value and report typed errors, and reads a Rust producer’s API from its built library instead of its source. Every consumer must be regenerated, and every hand-written producer or consumer updated. There are no compatibility shims.
IDL
Names are global: a declaration is identified by its bare name alone, and modules only group declarations and namespace C symbols.
- Set
version: "0.11.0". - Type names (records, enums, interfaces, callback interfaces, and error
domains), free-function names, and error-code names must each be unique
across the whole API. Two modules can no longer both declare a function
open; rename one (DuplicateFunctionName). A free function also can’t share its name with an error domain. - Qualified type references are removed: write
Contact, notcontacts.Contact(QualifiedTypeRef). usize,isize,u128,i128, andcharare reported asUnsupportedPrimitive, with a suggested replacement.DuplicateStructName,DuplicateEnumName,DuplicateInterfaceName,DuplicateCallbackInterfaceName, andDuplicateErrorNameare gone:DuplicateTypeNameandDuplicateErrorCodeNamereport those once.- A callback-interface method may return any type except an iterator or a
callback interface, and may set
throws: truewhen an error domain is in scope for its module. - A callback-interface parameter may be optional (
Cb?), as a top-level parameter of a function or interface member.
C ABI
{prefix}_abi_version()returns4.- Contract tables replace checksums.
{prefix}_{module}_checksum()and{PREFIX}_{MODULE}_CHECKSUMare gone; each top-level module exportsconst {prefix}_contract_entry* {prefix}_{module}_contract(size_t* out_len), one{id, hash}entry per declaration, sorted by id. The header defines{PREFIX}_{MODULE}_CONTRACT,{PREFIX}_{MODULE}_CONTRACT_LEN, and{prefix}_{module}_contract_check(), which returns the id of the first entry the library lacks or has with a different hash, or0. Adding a declaration no longer breaks deployed bindings. A hand-written producer returns the header’s table (see Producers in other languages). - Every callback vtable starts with a header,
uint32_t size; uint32_t flags; void (*free)(void*);, then the methods in declaration order (freeused to come last). Setsizetosizeofthe vtable andflagsto0. A producer rejects a vtable smaller than its own with-3. - Callback methods may return strings, bytes, buffers, and objects. A
string, bytes, or buffer return adds trailing
uint8_t** out_ptr, size_t* out_lenslots holding a run the consumer allocates with{prefix}_alloc; an object return is one strong reference. The producer adopts both. - A callback method declared
throwsmay report a positive code of its domain, with the code’s fields set by the new{prefix}_error_set_payload(err, ptr, len)(which copies). Every other callback failure still reaches the producer as-4. - An optional callback parameter passes a null vtable for none.
{prefix}_alloc(size_t len)is exported on every target (it waswasm32only) and returns a zero-filled run, or null for0.{prefix}_deallocis removed:{prefix}_free_bytesfrees both returned runs andallocruns, and is a no-op for length0.{prefix}_debug_live(-1)returns1when the producer counts and0when it doesn’t.- An async call the executor can’t start completes with
-1and a message. - A value buffer whose map repeats a key, and an iterator
_nextthat arrives while the same iterator is being advanced, fail with-3.
Rust producers
- The crates are consolidated.
weaveffi-abiis now theweaveffi::abimodule, andweaveffi-genmoved intoweaveffi-cli, which is a library as well as theweaveffibinary. Depend onweaveffialone. - Every callback-interface method must return
Result<T, weaveffi::ForeignError>; a plainTis a compile error.ForeignErrorgains apayloadfield anddomain::<E>(), which decodes a declared domain error. Mark a method#[weaveffi::throws]to let its consumer report the domain in scope. - The deferred-error API is removed:
ThunkScope,defer_foreign_error,raise_foreign_error, andtake_foreign_errorno longer exist, and a callback failure never unwinds or waits for the thunk. To propagate one, returnResult<T, ForeignError>from the exported function (which then needs an error domain in scope) and use?. - Accept an optional callback as
Option<Arc<dyn Trait>>. weaveffi::abi::decode_valueandBufferValue::read_valueareunsafe fn, because decoding an object token adopts a reference. A reader built withBufferReader::token_freerefuses tokens instead.- C-style enums implement the runtime trait
weaveffi::abi::CEnum, which replaces the generated__weaveffi_from_i32and__weaveffi_to_i32inherent methods. - The macro validates its module tree with the CLI’s validator and reports each error on the offending item. A type from another module tree that isn’t a record or rich enum fails with “is not a WeaveFFI record or rich enum”.
#[cfg]on an item applies to its thunks, metadata, and contract entries, and non-generic type aliases in the tree (pub type Id = u64;) are substituted. A#[cfg]on a member (a field, variant, or method) and an out-of-line submodule (mod x;) are compile errors.- The default async executor is a fixed pool of worker threads instead of a
thread per call. The
weavefficrate gains atokiofeature that runs futures on the current Tokio runtime, or on one it creates;weaveffi::set_spawnerstill overrides both.
CLI and configuration
- Library mode. The CLI no longer reads Rust source. The macro embeds
the API in the library (one exported
{PREFIX}_META_{HASH}static per declaration, or theweaveffi_metacustom section onwasm32), andgenerate,validate,diff, andextractbuild the crate withcargo build --liband read it from there, so generated bindings follow#[cfg].--library <path>reads an existing build instead, and--releasebuilds in release mode. See Library Mode. - The input is the crate. Set
[project] input = "."(instead of"src/lib.rs"), and pass a crate directory or itsCargo.tomlon the command line instead of a.rsfile. weaveffi extractprints the IDL a built library embeds (--library, or the project’s crate) instead of parsing a source file. Its--lenientflag is gone: what it prints always validates.generate --forceis gone, along with the input-hash freshness check it bypassed. Every run renders every selected target and rewrites only the files whose contents changed.[global]is gone, with itspre_generateandpost_generatehooks and everystrip_module_prefixkey (per target, too). Run formatters as a separate step. Targets that flatten module functions into one namespace use the bare function name, which global names keep collision-free.- One
namekey per target.[generators.cpp] namespace,swift.module_name,kotlin.package,node.package_name,wasm.package_name,python.package_name,dotnet.namespace,dart.package_name,go.module_path, andruby.gem_nameall becomename. Ruby’smodule_nameand Python’simport_namestay. [project] outdefaults tobindingsinstead ofgenerated, and generated files’ headers no longer name the input file.weaveffi devis new: it builds the debug library, generates, copies the library into the generated Python, Node.js, and Ruby packages, and prints how to point the other targets at it.weaveffi buildis new, andweaveffi packagewrites installable artifacts (wheels, npm tarballs, gems, anXCFrameworkSwiftPM package, a.nupkg) instead of source trees.package --build <crate>is gone:packagebuilds the project’s producer itself, or reads--binaries <dir>. The dist directory is-o, else[package] dist, elsedist. See Packaging.- The
weaveffi-clilibrary exposesproject::Project, which locates a project and generates it, for use from abuild.rs(with an IDL input or a previously built library).
Generated bindings
Regenerate every target. Beyond the ABI changes, a few generated APIs changed shape:
- Every callback-interface method in every language may now return a value
and, when it’s declared
throws, raise the module’s domain error. - Kotlin maps
u8,u16,u32, andu64toUByte,UShort,UInt, andULonginstead of the signed types. - Python ships one fully annotated module plus
py.typed; the separate.pyistub is gone. - The Node.js package loads a prebuilt addon when one matches the platform and compiles it only as a fallback.
- Load failures name the declaration that’s missing or changed, instead of reporting a checksum mismatch.
Samples
The six samples are now three: calculator (minimal), codec (the
value-buffer oracle, rebuilt around shared test vectors), and kvstore
(every feature). contacts, events, and async-demo were folded into
kvstore and removed, and every language’s conformance consumers follow
suit: three lanes per language. See Samples.
Migrating to schema 0.10 and ABI 3
WeaveFFI 0.24.0 shipped schema 0.10 and ABI revision 3. Coming from an older release, apply these changes first, then the ones above:
- Every symbol, type, constant, and macro carries the library’s prefix
(
{prefix}_error,{prefix}_free_bytes,{PREFIX}_API,{prefix}_kv_Store_open), the header is{library}.h, and generated packages and loaders are named after the library’s identity, never after WeaveFFI.[global] c_prefixand every per-target prefix key are gone; an IDL sets[package] c_prefixorlibrary, and a Rust producer’s prefix is its crate’s library name. - Strings cross as UTF-8
(ptr, len)runs everywhere, and returned strings are freed with{prefix}_free_bytes. - Async launchers drop the
_asyncsuffix, cancel tokens are reference counted, and runtime code-5means cancelled. - Map keys can’t be floats, and
cancellable: trueon a synchronous callable is an error. - The runtime is exported with
weaveffi::export_runtime!(), and a#[weaveffi::error]enum must implementDisplay. - The
[project]table andweaveffi initare new, and generation rewrites only changed files and deletes stale ones.
The changelog lists every release.