-
Notifications
You must be signed in to change notification settings - Fork 246
Description
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;
}