Open dtolnay opened 4 years ago
The current workaround for async calls is to implement them using a callback function and oneshot channel. In my work codebase that ends up looking like:
use futures::channel::oneshot;
#[cxx::bridge]
mod ffi {
extern "Rust" {
type DoThingAsync;
}
extern "C" {
fn doThing(
arg: Arg,
done: fn(Box<DoThingAsync>, ret: Ret),
ctx: Box<DoThingAsync>,
);
}
}
type DoThingAsync = oneshot::Sender<Ret>;
pub async fn do_thing(arg: Arg) -> Ret {
let (tx, rx) = oneshot::channel();
let context = Box::new(tx);
ffi::doThing(
arg,
|tx, ret| { let _ = tx.send(ret); },
context,
);
rx.await.unwrap()
}
void doThing(
Arg arg,
rust::Fn<void(rust::Box<DoThingAsync> ctx, Ret ret)> done,
rust::Box<DoThingAsync> ctx) noexcept {
doThingImpl(arg)
.then([done, ctx(std::move(ctx))](auto &&ret) mutable {
(*done)(std::move(ctx), ret);
});
}
@dtolnay Cxx is a fantastic library, thank you for it! I am interested in async
support, and have a few questions about the example above:
DoThingContext
should be DoThingAsync
?void doThing(
Arg arg,
rust::Fn<void(rust::Box<DoThingAsync>, Ret)> done,
rust::Box<DoThingAsync> ctx) noexcept {
...
(I.e., without ctx
and ret
in the function pointer signature.)
.then
, which I assume is non-blocking, but does seem to be an experimental feature at this point (https://en.cppreference.com/w/cpp/experimental/future/then). I tried replicating your code with std::async
using a lambda like so:
std::async(std::launch::async, [done, ctx(std::move(ctx))]() mutable {
std::this_thread::sleep_for(std::chrono::milliseconds(2000));
(*done)(std::move(ctx), "Some result");
});
...but this approach does not yield control back to Rust while sleeping in the C++ lambda. Do you have advise how to write the C++ part in a non-blocking way?
void doThing(
Arg arg,
rust::Fn<void(rust::Box<DoThingAsync> ctx, Ret ret)> ok,
rust::Fn<void(rust::Box<DoThingAsync> ctx, const std::string &exn)> fail,
rust::Box<DoThingAsync> ctx) noexcept {
doThingImpl(arg)
.thenTry([ok, fail, ctx(std::move(ctx))](auto &&res) mutable {
if (res.hasValue()) {
(*ok)(std::move(ctx), *res);
} else {
(*fail)(std::move(ctx), res.exception().what().toStdString());
}
});
}
@dtolnay Thank you very much for the prompt response! I will look into folly.
@dtolnay I've started to prototype a very rough implementation of async support in https://github.com/capickett/cxx/tree/cpp-futures.
I'm still getting a lay of the cxx
land, but my high-level thinking is the following.
cxx
-specific Future
type would be exposed -- i.e. will not take a dependency on boost or folly.Future::poll
and C++'s std::coroutine_handle<>::resume
and Awaiter
types.cxx::Future
and cxx::Promise
type to cxx.h
These types would primarily support C++11 - C++17 in non-coroutines codebases. The types would follow a similar "shared core state machine" approach that folly::Future
takes. The public API would look like:template <typename T>
struct Future {
// Immediately available futures
explicit Future(const T &);
explicit Future(T &&);
Future(Future &&) noexcept = default;
Future &operator=(Future &&) noexcept = default;
bool ready() const noexcept;
// Either throw if Future contains error, or wrap T in a Result type
T result() noexcept;
};
template <typename T>
struct Promise {
Promise() noexcept;
Promise(Promise &&) noexcept = default;
Promise &operator=(Promise &&) noexcept = default;
Future<T> getFuture() const noexcept;
void setValue(const T &value) noexcept;
void setValue(T &&value) noexcept;
};
CxxFuture
type in Rust that is repr(C)
and allows for receiving the future by value. This type with implement Future<Output = Result<T, Exception>
and will require that T
is a type that can be returned from a non-async C++ function. E.g. SharedType
, UniquePtr<OpaqueType>
, SharedPtr<OpaqueType>
, etc. Primitives should likely be supported as well.CxxFuture
.async
functions, and perform the necessary sugaring to treat them as Future returning functions, same as (3).What do you think of that approach? Feel free to look at the top two changes in my fork, they're not the cleanest, but show an example of calling an async function and awaiting it. I'm also doing some strange packing/unpacking (CxxFuture<T>
impls Future<Output = Result<UniquePtr<T, Exception>>
) of the type that I hope to clean up.
I'm curious to know if there's been more progress on this.
This is implemented in https://github.com/pcwalton/cxx-async
The fine details of this need to be worked out.
The dream would be that this:
knows how to call this:
where
Future
is any type with a suitable API to supportco_await
/co_return
(std::future
,folly::coro::Task
,folly::Future
,folly::SemiFuture
, etc).