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, function prototypes, error/memory helpers
generated/c/weaveffi.cEmpty placeholder for future convenience wrappers (kept so projects can link a single TU if desired)

Type mapping

IDL typeC parameter typeC return type
i32int32_tint32_t
u32uint32_tuint32_t
i64int64_tint64_t
f64doubledouble
boolboolbool
stringconst uint8_t* ptr, size_t lenconst 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*
Enumweaveffi_m_Eweaveffi_m_E
T? (value)const T* (NULL = absent)T* (NULL = absent)
[T]const T* items, size_t items_lenT* + size_t* out_len

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

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

Example IDL → generated code

version: "0.3.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);

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

Build instructions

The runnable example uses the calculator sample crate.

macOS:

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

Linux:

cargo build -p calculator

cd examples/c
cc -I ../../generated/c main.c -L ../../target/debug -lcalculator -o c_example
LD_LIBRARY_PATH=../../target/debug ./c_example

Windows:

cargo build -p calculator
cd examples\c
cl /I ..\..\generated\c main.c /link calculator.lib
.\main.exe

See examples/c/main.c for end-to-end usage.

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.

Async support

Async functions (async: true) generate a callback-based variant with the suffix _async. The wrapper accepts a function pointer whose signature mirrors the synchronous return and an opaque void* context. WeaveFFI invokes the callback once with either a result or an error.

typedef void (*weaveffi_demo_fetch_cb)(
    void* context,
    weaveffi_error* err,
    const char* result);

void weaveffi_demo_fetch_async(
    int32_t id,
    weaveffi_demo_fetch_cb callback,
    void* context);

Cancellable functions also accept a weaveffi_cancel_token*. See Async functions for the full pattern.

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 was not 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 is empty — that file is intentionally a placeholder. All declarations live in weaveffi.h.