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++

The C++ generator emits a single header-only library weaveffi.hpp that wraps the C ABI with idiomatic C++ types. Structs use RAII classes with move semantics, errors become exceptions, and async functions return std::future. A CMakeLists.txt is included for easy integration.

Generated artifacts

  • generated/cpp/weaveffi.hpp — header-only C++ bindings (extern “C” declarations, RAII wrapper classes, enum classes, inline function wrappers)
  • generated/cpp/CMakeLists.txt — INTERFACE library target for CMake
  • generated/cpp/README.md — build instructions

RAII approach

All structs are wrapped as C++ classes that own a void* handle to Rust-allocated data. The destructor calls the C ABI _destroy function, so resources are freed automatically when the object goes out of scope — no manual cleanup required.

Copy construction and copy assignment are deleted to prevent double-free bugs. Move construction and move assignment are supported, transferring ownership by nulling out the source handle.

This design means you can use standard C++ patterns like returning structs from functions, storing them in containers via std::move, and relying on stack unwinding for cleanup during exceptions.

Generated code examples

Given this IDL definition:

version: "0.1.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 }
          - { name: contact_type, type: ContactType }

    functions:
      - name: create_contact
        params:
          - { name: name, type: string }
          - { name: email, type: "string?" }
          - { name: age, type: i32 }
        return: Contact

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

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

      - name: count_contacts
        params: []
        return: i32

      - name: fetch_contact
        async: true
        params:
          - { name: id, type: i32 }
        return: Contact

Enums

Enums map to C++ enum class backed by int32_t:

enum class ContactType : int32_t {
    Personal = 0,
    Work = 1,
    Other = 2
};

Structs (RAII wrapper classes)

Structs are wrapped as C++ classes holding a void* handle to the Rust-allocated data. The destructor calls the C ABI destroy function. Field access is through getter methods that call the C ABI getters:

class Contact {
    void* handle_;

public:
    explicit Contact(void* h) : handle_(h) {}

    ~Contact() {
        if (handle_) weaveffi_contacts_Contact_destroy(
            static_cast<weaveffi_contacts_Contact*>(handle_));
    }

    Contact(const Contact&) = delete;
    Contact& operator=(const Contact&) = delete;

    Contact(Contact&& other) noexcept : handle_(other.handle_) {
        other.handle_ = nullptr;
    }

    Contact& operator=(Contact&& other) noexcept {
        if (this != &other) {
            if (handle_) weaveffi_contacts_Contact_destroy(
                static_cast<weaveffi_contacts_Contact*>(handle_));
            handle_ = other.handle_;
            other.handle_ = nullptr;
        }
        return *this;
    }

    void* handle() const { return handle_; }

    std::string name() const {
        const char* raw = weaveffi_contacts_Contact_get_name(
            static_cast<const weaveffi_contacts_Contact*>(handle_));
        std::string ret(raw);
        weaveffi_free_string(raw);
        return ret;
    }

    std::optional<std::string> email() const {
        auto* raw = weaveffi_contacts_Contact_get_email(
            static_cast<const weaveffi_contacts_Contact*>(handle_));
        if (!raw) return std::nullopt;
        std::string ret(raw);
        weaveffi_free_string(raw);
        return ret;
    }

    int32_t age() const {
        return weaveffi_contacts_Contact_get_age(
            static_cast<const weaveffi_contacts_Contact*>(handle_));
    }

    ContactType contact_type() const {
        return static_cast<ContactType>(
            weaveffi_contacts_Contact_get_contact_type(
                static_cast<const weaveffi_contacts_Contact*>(handle_)));
    }
};

Functions

Module functions are generated as inline free functions in the weaveffi namespace. Every function checks the error struct after calling the C ABI and throws WeaveFFIError on failure:

namespace weaveffi {

inline Contact contacts_create_contact(
    const std::string& name,
    const std::optional<std::string>& email,
    int32_t age)
{
    weaveffi_error err{};
    auto result = weaveffi_contacts_create_contact(
        name.c_str(),
        email.has_value() ? email.value().c_str() : nullptr,
        age, &err);
    if (err.code != 0) {
        std::string msg(err.message ? err.message : "unknown error");
        int32_t code = err.code;
        weaveffi_error_clear(&err);
        throw WeaveFFIError(code, msg);
    }
    return Contact(result);
}

inline std::optional<Contact> contacts_find_contact(int32_t id) {
    weaveffi_error err{};
    auto result = weaveffi_contacts_find_contact(id, &err);
    if (err.code != 0) {
        std::string msg(err.message ? err.message : "unknown error");
        int32_t code = err.code;
        weaveffi_error_clear(&err);
        throw WeaveFFIError(code, msg);
    }
    if (!result) return std::nullopt;
    return Contact(result);
}

inline std::vector<Contact> contacts_list_contacts() {
    size_t out_len = 0;
    weaveffi_error err{};
    auto result = weaveffi_contacts_list_contacts(&out_len, &err);
    if (err.code != 0) { /* ... throw ... */ }
    std::vector<Contact> ret;
    ret.reserve(out_len);
    for (size_t i = 0; i < out_len; ++i)
        ret.emplace_back(Contact(result[i]));
    return ret;
}

inline int32_t contacts_count_contacts() {
    weaveffi_error err{};
    auto result = weaveffi_contacts_count_contacts(&err);
    if (err.code != 0) { /* ... throw ... */ }
    return result;
}

} // namespace weaveffi

Type mapping reference

IDL typeC++ typePassed as parameter
i32int32_tint32_t
u32uint32_tuint32_t
i64int64_tint64_t
f64doubledouble
boolboolbool
stringstd::stringconst std::string&
bytesstd::vector<uint8_t>const std::vector<uint8_t>&
handlevoid*void*
StructNameStructNameconst StructName&
EnumNameEnumName (enum class)EnumName
T?std::optional<T>const std::optional<T>&
[T]std::vector<T>const std::vector<T>&
{K: V}std::unordered_map<K, V>const std::unordered_map<K, V>&

Error handling via exceptions

Native errors are surfaced through a WeaveFFIError class that extends std::runtime_error. Every generated function checks the C ABI error struct after each call and throws on non-zero error codes:

class WeaveFFIError : public std::runtime_error {
    int32_t code_;

public:
    WeaveFFIError(int32_t code, const std::string& msg)
        : std::runtime_error(msg), code_(code) {}
    int32_t code() const { return code_; }
};

When the IDL defines custom error codes, the generator also emits specific exception subclasses (e.g. NotFoundError, ValidationError) that inherit from WeaveFFIError, and the error-checking logic uses a switch statement to throw the most specific type.

Catch errors in consumer code:

try {
    auto contact = weaveffi::contacts_find_contact(42);
} catch (const weaveffi::WeaveFFIError& e) {
    std::cerr << "Error " << e.code() << ": " << e.what() << std::endl;
}

Async support via std::future

Async IDL functions generate wrappers that return std::future<T>. Under the hood, the wrapper creates a std::promise, passes a callback to the C ABI _async function, and resolves (or rejects) the promise when the callback fires:

inline std::future<Contact> contacts_fetch_contact(int32_t id) {
    auto* promise_ptr = new std::promise<Contact>();
    auto future = promise_ptr->get_future();
    weaveffi_contacts_fetch_contact_async(id,
        [](void* context, weaveffi_error* err,
           weaveffi_contacts_Contact* result) {
            auto* p = static_cast<std::promise<Contact>*>(context);
            if (err && err->code != 0) {
                std::string msg(err->message ? err->message : "unknown error");
                p->set_exception(std::make_exception_ptr(
                    WeaveFFIError(err->code, msg)));
            } else {
                p->set_value(Contact(result));
            }
            delete p;
        }, static_cast<void*>(promise_ptr));
    return future;
}

Use it with .get() (blocking) or integrate with your application’s event loop:

auto future = weaveffi::contacts_fetch_contact(42);
auto contact = future.get();
std::cout << contact.name() << std::endl;

Cancellable async functions accept an optional weaveffi_cancel_token* parameter.

CMake integration

The generated CMakeLists.txt defines an INTERFACE library target called weaveffi_cpp:

cmake_minimum_required(VERSION 3.14)
project(weaveffi_cpp)
add_library(weaveffi_cpp INTERFACE)
target_include_directories(weaveffi_cpp INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(weaveffi_cpp INTERFACE weaveffi)
target_compile_features(weaveffi_cpp INTERFACE cxx_std_17)

To use in your project, add the generated directory as a subdirectory and link your target:

add_subdirectory(path/to/generated/cpp)
add_executable(myapp main.cpp)
target_link_libraries(myapp weaveffi_cpp)

This automatically adds the header include path, links the weaveffi native library, and requires C++17. Then include the header:

#include "weaveffi.hpp"

Make sure the Rust-built shared library (libweaveffi.dylib, libweaveffi.so, or weaveffi.dll) is discoverable at link and run time.