Skip to content

CPP: async guest bindings sketchΒ #1426

@ricochet

Description

@ricochet

While we could go with exactly the choices made for async c bindgen, I'd like to sketch a C++ native solution.

C++20 introduces coroutines which are a much closer fit to the co-operative threads design in the component model.

The perennial challenge with C++ is which minimum version should we support? I see where we lean into C++ 17 features today, but could/should we raise the bar to C++20? I started sketching what it might look like assuming we could depend on C++20 and also assuming we wouldn't go to C++23.

cc @cpetig

Some basic design decisions:

  • Should we use exceptions for async failures? These appear to be uncommon in modern C++ for perf reasons and we should use std::expected<T, AsyncError> instead.
  • Move semantics for all async operations, and have them only use owned values (to prevent use-after-move)
  • No references/borrows in async contexts (to prevent dangling)
  • All operations are cancellable
  • RAII for automatic handle cleanup

Just a heads up that I'm knocking some rust off (pun intended) after having not written C++ professionally in several years. So please don't take my proposal here as authoritative as there's a solid chance I've missed some features we may want to take advantage of.

Usage example

interface example {
    get-value: async func() -> u32;
}
// Bindings
namespace example {
    // Async import
    Task<uint32_t> GetValue();

    // Implementation (export)
    namespace exports {
        uint32_t GetValue() {
            // User implementation
            return 42;
        }
    }
}

// Usage
Task<uint32_t> example_task = example::GetValue();
uint32_t value = co_await example_task;

Sketches on implementation details

// Cancellation support
auto cancel() -> std::optional<T> {
    uint32_t status = writer_->vtable_->cancel_write(writer_->handle_);
    if (status == (uint32_t)AsyncStatus::Cancelled) {
        // Successfully cancelled, return original value
        return std::move(value_);
    }
    return std::nullopt;  // Already completed
}

Sketch for reading values from a stream:

// Read one value (returns nullopt on EOF)
auto next() -> Task<std::optional<T>> {
    T value;
    auto result = co_await read(std::span<T>(&value, 1));
    if (result.count == 0) {
        co_return std::nullopt;
    }
    co_return std::move(value);
}

// Collect all remaining values
auto collect() -> Task<std::vector<T>> {
    std::vector<T> results;
    while (auto val = co_await next()) {
        results.push_back(std::move(*val));
    }
    co_return results;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    gen-cppRelated to the C++ code generator

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions