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

C

Overview

The C target emits the canonical C header and a thin reference C file that every other WeaveFFI target ultimately speaks to. All cross-language bindings sit on top of these symbols, so the C output is also the easiest way to inspect what the IDL compiles to.

What gets generated

FilePurpose
generated/c/weaveffi.hPublic header: opaque types, enums, interfaces, function prototypes, error/memory helpers
generated/c/weaveffi.cDefault weaveffi_alloc/weaveffi_dealloc implementations (used by the Wasm JS glue); producers that ship their own allocator can omit it

Type mapping

IDL typeC parameter typeC return type
i32int32_tint32_t
u32uint32_tuint32_t
i64int64_tint64_t
u64uint64_tuint64_t
i8int8_tint8_t
i16int16_tint16_t
u8uint8_tuint8_t
u16uint16_tuint16_t
f32floatfloat
f64doubledouble
boolboolbool
stringconst char* (NUL-terminated UTF-8)const char*
bytesconst uint8_t* ptr, size_t lenconst uint8_t* + size_t* out_len
handleweaveffi_handle_tweaveffi_handle_t
Structconst weaveffi_m_S*weaveffi_m_S*
Interfaceconst weaveffi_m_I* (borrowed)weaveffi_m_I* (owned)
Enum (plain)weaveffi_m_Eweaveffi_m_E
Enum (rich)const weaveffi_m_E*weaveffi_m_E*
T? (value)const T* (NULL = absent)T* (NULL = absent)
[T]const T* items, size_t items_lenT* + size_t* out_len
iter<T>n/aopaque iterator handle (see Iterators)

C ABI symbol naming follows a strict convention:

KindPatternExample
Functionweaveffi_{module}_{function}weaveffi_contacts_create_contact
Struct typeweaveffi_{module}_{Struct}weaveffi_contacts_Contact
Struct createweaveffi_{module}_{Struct}_createweaveffi_contacts_Contact_create
Struct destroyweaveffi_{module}_{Struct}_destroyweaveffi_contacts_Contact_destroy
Struct getterweaveffi_{module}_{Struct}_get_{field}weaveffi_contacts_Contact_get_name
Enum typeweaveffi_{module}_{Enum}weaveffi_contacts_ContactType
Enum variantweaveffi_{module}_{Enum}_{Variant}weaveffi_contacts_ContactType_Personal
Interface typeweaveffi_{module}_{Interface}weaveffi_kv_Store
Interface memberweaveffi_{module}_{Interface}_{member}weaveffi_kv_Store_open
Interface destroyweaveffi_{module}_{Interface}_destroyweaveffi_kv_Store_destroy
Error enumweaveffi_{module}_{Domain}weaveffi_kv_KvError
Error constantweaveffi_{module}_{Domain}_{Code}weaveffi_kv_KvError_KeyNotFound
Callback typedefweaveffi_{module}_{Callback}_fnweaveffi_events_OnMessage_fn
Listener registerweaveffi_{module}_register_{listener}weaveffi_events_register_message_listener
Listener unregisterweaveffi_{module}_unregister_{listener}weaveffi_events_unregister_message_listener
Async callbackweaveffi_{module}_{function}_callbackweaveffi_tasks_run_task_callback
Async launcherweaveffi_{module}_{function}_asyncweaveffi_tasks_run_task_async
Iterator typeweaveffi_{module}_{Function}Iteratorweaveffi_events_GetMessagesIterator
Iterator nextweaveffi_{module}_{Function}Iterator_nextweaveffi_events_GetMessagesIterator_next
Iterator destroyweaveffi_{module}_{Function}Iterator_destroyweaveffi_events_GetMessagesIterator_destroy

{Function} is the function name converted to PascalCase (get_messagesGetMessages). An iterator returned by an interface method nests under the interface instead: weaveffi_kv_Store_ListKeysIterator. Interface members and async launchers compose the same way (weaveffi_kv_Store_compact_async).

When the IDL sets c_prefix, every symbol, including the runtime helpers, is rewritten with the new prefix.

Example IDL → generated code

version: "0.5.0"
modules:
  - name: contacts
    enums:
      - name: ContactType
        variants:
          - { name: Personal, value: 0 }
          - { name: Work, value: 1 }
          - { name: Other, value: 2 }

    structs:
      - name: Contact
        fields:
          - { name: name, type: string }
          - { name: email, type: "string?" }
          - { name: age, type: i32 }

    functions:
      - name: create_contact
        params:
          - { name: first_name, type: string }
          - { name: last_name, type: string }
        return: Contact

      - name: find_contact
        params:
          - { name: id, type: "i32?" }
        return: "Contact?"

      - name: list_contacts
        params: []
        return: "[Contact]"

      - name: count_contacts
        params: []
        return: i32

The header opens with an include guard, standard headers, an extern "C" block, and the shared error/memory helpers:

#ifndef WEAVEFFI_H
#define WEAVEFFI_H

#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef uint64_t weaveffi_handle_t;

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);

In the real output each prototype is prefixed with a WEAVEFFI_API visibility macro (and deprecated functions with WEAVEFFI_DEPRECATED), omitted here for brevity. See Symbol visibility for what it does and when you need it.

Structs become forward-declared opaque typedefs reached via create/destroy/getter functions:

typedef struct weaveffi_contacts_Contact weaveffi_contacts_Contact;

weaveffi_contacts_Contact* weaveffi_contacts_Contact_create(
    const char* name,
    const char* email,
    int32_t age,
    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);

Enums turn into typed enum declarations with prefixed variants:

typedef enum {
    weaveffi_contacts_ContactType_Personal = 0,
    weaveffi_contacts_ContactType_Work = 1,
    weaveffi_contacts_ContactType_Other = 2
} weaveffi_contacts_ContactType;

Optionals and lists use pointer-with-sentinel and pointer+length pairs:

int32_t* weaveffi_store_find(const int32_t* id, weaveffi_error* out_err);

weaveffi_contacts_Contact** weaveffi_contacts_list_contacts(
    size_t* out_len,
    weaveffi_error* out_err);

Every function takes a trailing weaveffi_error* out_err. On failure out_err->code is non-zero and out_err->message points at a Rust-allocated string the consumer must clear:

weaveffi_error err = {0, NULL};
int32_t total = weaveffi_contacts_count_contacts(&err);
if (err.code != 0) {
    fprintf(stderr, "Error %d: %s\n", err.code, err.message);
    weaveffi_error_clear(&err);
    return 1;
}

Interfaces

An interfaces: entry lowers to a forward-declared opaque struct plus one prototype per member. Constructors return an owned pointer, methods take a leading const {tag}* self argument before their declared parameters, statics take no self, and every interface gets an implicit _destroy. From the kvstore sample’s Store:

typedef struct weaveffi_kv_Store weaveffi_kv_Store;

/* Constructor: returns a new owned instance. */
weaveffi_kv_Store* weaveffi_kv_Store_open(const char* path, weaveffi_error* out_err);

/* Static: no self slot. */
int64_t weaveffi_kv_Store_default_capacity(weaveffi_error* out_err);

/* Methods: an implicit leading self slot. */
bool weaveffi_kv_Store_delete(const weaveffi_kv_Store* self, const char* key,
                              weaveffi_error* out_err);
int64_t weaveffi_kv_Store_count(const weaveffi_kv_Store* self, weaveffi_error* out_err);

/* Implicit destructor: releases the object. */
void weaveffi_kv_Store_destroy(weaveffi_kv_Store* self);

Ownership follows the reference direction: an interface parameter (such as const weaveffi_kv_Store* store on weaveffi_kv_stats_get_stats) is borrowed for the duration of the call, while every pointer returned by a constructor or function is owned by the consumer, who must eventually pass it to _destroy. Iterator-returning and async methods follow the same shapes as free functions with the self slot in front: Store.list_keys yields a weaveffi_kv_Store_ListKeysIterator handle, and the async Store.compact appears under Async support.

Typed errors

C is the raw ABI surface, so throwing and non-throwing callables look identical: every prototype carries the trailing weaveffi_error* out_err, and the consumer checks err.code after each call. What a module’s error domain adds is a typed C enum naming the codes its throws: true callables can report, so consumers match on names instead of magic numbers. From the kvstore sample’s KvError domain:

/** Error codes reported by throwing functions in the `kv` module tree. */
typedef enum {
    weaveffi_kv_KvError_KeyNotFound = 1001,
    weaveffi_kv_KvError_Expired = 1002,
    weaveffi_kv_KvError_StoreFull = 1003,
    weaveffi_kv_KvError_IoError = 1004
} weaveffi_kv_KvError;

A callable declared with throws: true can set any of these codes; a callable without throws can only fail with the reserved codes (-2 for a producer panic, 1 for a marshalling failure). See the Error Handling guide for the full code table.

Symbol visibility

Every function prototype is tagged with a WEAVEFFI_API macro that the header defines near the top:

#ifndef WEAVEFFI_API
#  if defined(_WIN32) || defined(__CYGWIN__)
#    ifdef WEAVEFFI_BUILD
#      define WEAVEFFI_API __declspec(dllexport)
#    else
#      define WEAVEFFI_API __declspec(dllimport)
#    endif
#  elif defined(__GNUC__) && (__GNUC__ >= 4)
#    define WEAVEFFI_API __attribute__((visibility("default")))
#  else
#    define WEAVEFFI_API
#  endif
#endif

This covers the two ways the header is used:

  • Consuming a prebuilt library (the common case) needs nothing extra. On Windows the prototypes resolve to __declspec(dllimport); everywhere else the macro is harmless.
  • Implementing the header (a C, C++, or Zig backend that supplies the symbols instead of calling them) relies on the macro to stay exportable. Under hidden default visibility (-fvisibility=hidden, the release-build norm and the MSVC default) an untagged definition is local and ships no usable symbol. On GCC and Clang the macro applies visibility("default"), so your definitions export with no extra flags.

When you implement the header on Windows, compile your library with WEAVEFFI_BUILD defined so the macro switches to __declspec(dllexport):

cc -DWEAVEFFI_BUILD -shared mylib.c -o mylib.dll

Deprecated functions carry a companion WEAVEFFI_DEPRECATED("...") macro that expands to __declspec(deprecated(...)) on MSVC and __attribute__((deprecated(...))) on GCC and Clang.

When the IDL sets c_prefix, both macros follow it: a c_prefix of acme yields ACME_API, ACME_BUILD, and ACME_DEPRECATED, so two WeaveFFI-generated libraries can coexist in one translation unit without colliding.

Rich (algebraic) enums

An enum whose variants declare fields is a rich (algebraic) enum, a sum type with associated data. Unlike a plain C-style enum (a bare int32_t discriminant), a rich enum crosses the ABI as an opaque object pointer, exactly like a struct: the producer owns the payload and the consumer holds a handle. A plain _Tag enum names the discriminants, then constructors, a tag reader, per-variant getters, and a destructor operate on the handle. From the shapes sample (Shape = Empty | Circle{radius} | Rectangle{width,height} | Labeled{label,count}):

typedef enum {
    weaveffi_shapes_Shape_Empty = 0,
    weaveffi_shapes_Shape_Circle = 1,
    weaveffi_shapes_Shape_Rectangle = 2,
    weaveffi_shapes_Shape_Labeled = 3
} weaveffi_shapes_Shape_Tag;

typedef struct weaveffi_shapes_Shape weaveffi_shapes_Shape;

int32_t weaveffi_shapes_Shape_tag(const weaveffi_shapes_Shape* self);

weaveffi_shapes_Shape* weaveffi_shapes_Shape_Empty_new(weaveffi_error* out_err);
weaveffi_shapes_Shape* weaveffi_shapes_Shape_Circle_new(double radius, weaveffi_error* out_err);
weaveffi_shapes_Shape* weaveffi_shapes_Shape_Rectangle_new(float width, float height, weaveffi_error* out_err);
weaveffi_shapes_Shape* weaveffi_shapes_Shape_Labeled_new(const char* label, uint8_t count, weaveffi_error* out_err);

double weaveffi_shapes_Shape_Circle_get_radius(const weaveffi_shapes_Shape* self);
float weaveffi_shapes_Shape_Rectangle_get_width(const weaveffi_shapes_Shape* self);
float weaveffi_shapes_Shape_Rectangle_get_height(const weaveffi_shapes_Shape* self);
const char* weaveffi_shapes_Shape_Labeled_get_label(const weaveffi_shapes_Shape* self);
uint8_t weaveffi_shapes_Shape_Labeled_get_count(const weaveffi_shapes_Shape* self);

void weaveffi_shapes_Shape_destroy(weaveffi_shapes_Shape* self);

Read _tag, then call only the matching variant’s getters. A getter that returns a const char* hands back Rust-owned memory to free with weaveffi_free_string:

weaveffi_error err = {0, NULL};
weaveffi_shapes_Shape* shape = weaveffi_shapes_Shape_Circle_new(2.0, &err);

if (weaveffi_shapes_Shape_tag(shape) == weaveffi_shapes_Shape_Circle) {
    printf("radius = %f\n", weaveffi_shapes_Shape_Circle_get_radius(shape));
}

const char* text = weaveffi_shapes_describe(shape, &err);
printf("%s\n", text);
weaveffi_free_string(text);

weaveffi_shapes_Shape_destroy(shape);

The consumer owns every weaveffi_shapes_Shape* returned by a constructor or by a function such as weaveffi_shapes_scale; release each one with weaveffi_shapes_Shape_destroy.

Build instructions

The runnable consumer uses the contacts sample crate and its conformance program.

macOS:

cargo build -p contacts
weaveffi generate samples/contacts/contacts.yml -o generated

cc -I generated/c conformance/c/contacts.c -L target/debug -lcontacts -o c_contacts
DYLD_LIBRARY_PATH=target/debug ./c_contacts

Linux:

cargo build -p contacts
weaveffi generate samples/contacts/contacts.yml -o generated

cc -I generated/c conformance/c/contacts.c -L target/debug -lcontacts -o c_contacts
LD_LIBRARY_PATH=target/debug ./c_contacts

Windows:

cargo build -p contacts
weaveffi generate samples\contacts\contacts.yml -o generated
cl /I generated\c conformance\c\contacts.c /link contacts.lib
.\contacts.exe

See conformance/c/ for end-to-end consumers of every sample.

Memory and ownership

Rust always owns memory it allocates. Strings and byte buffers returned across the boundary must be freed by the consumer with the matching helper:

const char* name = weaveffi_contacts_Contact_get_name(contact);
printf("Name: %s\n", name);
weaveffi_free_string(name);

size_t len;
const uint8_t* data = weaveffi_storage_get_data(&len, &err);
weaveffi_free_bytes((uint8_t*)data, len);

For struct handles, call the matching _destroy symbol when the consumer is done. Borrowed parameters (const T*, string/bytes inputs) remain owned by the caller for the duration of the call only.

Callbacks and listeners

A callbacks: entry becomes a function-pointer typedef whose parameters mirror the IDL signature plus a trailing opaque void* context. A listeners: entry becomes a register/unregister pair built on that typedef. From the events sample:

typedef void (*weaveffi_events_OnMessage_fn)(const char* message, void* context);

uint64_t weaveffi_events_register_message_listener(
    weaveffi_events_OnMessage_fn callback,
    void* context);
void weaveffi_events_unregister_message_listener(uint64_t id);

The contract:

  • register_* stores the (callback, context) pair and returns a uint64_t subscription id. Pass that id to unregister_* to stop delivery.
  • context is opaque to the producer and is passed back verbatim as the last argument of every invocation. It must stay valid until the listener is unregistered.
  • The producer invokes the callback on its own thread, whenever the event fires. The callback must be thread-safe and must not assume it runs on the registering thread.
  • Pointer arguments (e.g. const char* message) are only valid for the duration of the invocation; copy anything that must outlive it.
static void on_message(const char* message, void* context) {
    int* count = context;       /* runs on the producer's thread */
    (*count)++;
}

weaveffi_error err = {0, NULL};
int count = 0;
uint64_t id = weaveffi_events_register_message_listener(on_message, &count);
weaveffi_events_send_message("hello", &err);   /* fires the listener */
weaveffi_events_unregister_message_listener(id);

Async support

Async functions (async: true) get no synchronous prototype. Each one emits a per-function callback typedef, (void* context, weaveffi_error* err, <result slots>), and a launcher with the _async suffix. From the async-demo sample:

typedef void (*weaveffi_tasks_run_task_callback)(
    void* context,
    weaveffi_error* err,
    weaveffi_tasks_TaskResult* result);

void weaveffi_tasks_run_task_async(
    const char* name,
    weaveffi_tasks_run_task_callback callback,
    void* context);

The launcher returns immediately; WeaveFFI invokes the callback exactly once, with either a result or a populated error, from the producer’s worker thread.

Ownership inside the callback follows the async contract. Result buffers (strings, bytes, arrays, map buffers, boxed optional scalars) are borrowed: they stay owned by the producer and are valid only for the callback’s duration, so copy anything you need before returning and don’t free them. Owned-object results (records, rich enums, interfaces, including optional ones) are the exception: the callback receives ownership of the pointer and must eventually pass it to the matching _destroy. The err struct is likewise borrowed; copy its code and message inside the callback.

For cancellable: true functions the launcher gains a weaveffi_cancel_token* slot before the callback, and the runtime provides the token lifecycle. Async interface methods follow the same shape with the leading self slot; from the kvstore sample’s async cancellable Store.compact:

typedef void (*weaveffi_kv_Store_compact_callback)(
    void* context,
    weaveffi_error* err,
    int64_t result);

void weaveffi_kv_Store_compact_async(
    const weaveffi_kv_Store* self,
    weaveffi_cancel_token* cancel_token,
    weaveffi_kv_Store_compact_callback callback,
    void* context);

weaveffi_cancel_token* weaveffi_cancel_token_create(void);
void weaveffi_cancel_token_cancel(weaveffi_cancel_token* token);
bool weaveffi_cancel_token_is_cancelled(const weaveffi_cancel_token* token);
void weaveffi_cancel_token_destroy(weaveffi_cancel_token* token);

See Async functions for the full pattern.

Iterators

Functions returning iter<T> produce an opaque iterator handle plus _next/_destroy functions instead of a materialized list. From the events sample (get_messages returns iter<string>):

typedef struct weaveffi_events_GetMessagesIterator weaveffi_events_GetMessagesIterator;

weaveffi_events_GetMessagesIterator* weaveffi_events_get_messages(
    weaveffi_error* out_err);
int32_t weaveffi_events_GetMessagesIterator_next(
    weaveffi_events_GetMessagesIterator* iter,
    const char** out_item,
    weaveffi_error* out_err);
void weaveffi_events_GetMessagesIterator_destroy(
    weaveffi_events_GetMessagesIterator* iter);

_next writes the next element into the one-slot out-param and returns 1, or returns 0 when exhausted (leaving *out_item untouched). Failures are reported through out_err, so check it after the loop ends. Element ownership follows the usual return rules; each next hands over an element the consumer now owns, so here each const char* must be freed with weaveffi_free_string. Call _destroy exactly once when done, even if iteration stopped early:

weaveffi_error err = {0, NULL};
weaveffi_events_GetMessagesIterator* iter = weaveffi_events_get_messages(&err);
const char* item = NULL;
while (weaveffi_events_GetMessagesIterator_next(iter, &item, &err) == 1) {
    printf("%s\n", item);
    weaveffi_free_string(item);
}
if (err.code != 0) { /* a failing step ended the loop */ }
weaveffi_events_GetMessagesIterator_destroy(iter);

The higher-level targets wrap exactly these three symbols in their native lazy idioms; only the C surface exposes them raw.

Troubleshooting

  • undefined reference to weaveffi_*: make sure the linker sees the cdylib (-L target/debug -l<your-crate>). The header alone is not enough.
  • Crashes inside weaveffi_free_string: the pointer wasn’t Rust-allocated. Only free pointers returned from a generated getter or function.
  • error: unknown type weaveffi_handle_t: the consumer included the header without <stdint.h>. Include order matters; the generated header pulls in the standard integer typedefs explicitly.
  • weaveffi.c looks nearly empty: that file only carries the default weaveffi_alloc/weaveffi_dealloc implementations for Wasm producers. All declarations live in weaveffi.h.