-
Notifications
You must be signed in to change notification settings - Fork 242
Description
Summary
We propose adding support for async fn syntax in impls of generated Server traits.
Motivation
Suppose we have this interface defined in a capnp schema file example.capnp:
interface Foo {
bar @0 (x :UInt16) -> (y :UInt16);
}Currently, capnpc-rust generates the following code in example_capnp.rs:
pub mod foo {
trait Server {
fn bar(&mut self, foo::BarParams, foo::BarResults) -> Promise<(), Error>;
}
// ...
}Users very often need to be able to run asynchronous code from within method bodies. The Promise type makes that possible; Promise<T, E> is essentially Box<dyn <Future<Output=Result<T,E>> + 'static>. A promise can be constructed like this:
Promise::from_future(async move {
// ... do some asynchronous stuff here
})However, there are some problems with this setup:
-
Rust users accustomed to
async/awaitfind it annoying to need to manually construct aPromiselike this. See Async/.await server impl #168 for some discussion. -
The
?operator does not work inside an RPC method body without thetry_trait_v2unstable feature. (We currently work around this with apry!()macro. -
There's no way to get access to the borrowed
selfparameter from within theasynccontext, because the future needs to be'static.
Fortunately, thanks to some new-ish features in the Rust compiler, we should now be able to address these problems.
Solution
We will adjust the generated trait to be:
pub mod foo {
trait Server {
fn bar(self: Rc<Self>,
foo::BarParams,
foo::BarResults) -> impl Future<Output=Result<(), Error>> + 'static;
}
// ...
}Then, user-provided implementations can use the async fn syntax like this:
impl foo::Server for FooImpl {
async fn bar(self: Rc<Self>, foo::BarParams, foo::BarResults) -> Result<(), Error> {
// ... do some asynchronous stuff here, possibly using the `?` operator
}
}To make the self parameter accessible within the method while staying compatible with the 'static lifetime bound, we need to pass it as an Rc<Self> rather than a borrowed reference. You might worry that this would incur extra ref-counting cost, but we are actually already doing this refcounting and incrementing (and eventually decrementing) the count on every call:
capnproto-rust/capnp-rpc/src/local.rs
Line 420 in 58977b7
| let inner = self.inner.clone(); |
Open Question
Should self be of type Rc<Self> or Rc<RefCell<Self>>? The latter is arguably more similar to the current setup (i.e. &mut self), but it has some downsides:
- the syntax
self: Rc<RefCell<Self>>does not work becauseRefCelldoes not directly deref to Self. - A
RefCell::borrow_mut()call that spans an.awaitcan easily cause a double-borrow panic. We probably want to be careful about making this footgun too prominently accessible.
Rc<Self> avoids these problems, but would require users to provide their own interior mutability if needed.
Drawbacks
This will require all existing RPC server impls to be updated.