Skip to content

async fn methods for RPC Server traits #577

@dwrensha

Description

@dwrensha

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:

  1. Rust users accustomed to async/await find it annoying to need to manually construct a Promise like this. See Async/.await server impl #168 for some discussion.

  2. The ? operator does not work inside an RPC method body without the try_trait_v2 unstable feature. (We currently work around this with a pry!() macro.

  3. There's no way to get access to the borrowed self parameter from within the async context, 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:

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:

  1. the syntax self: Rc<RefCell<Self>> does not work because RefCell does not directly deref to Self.
  2. A RefCell::borrow_mut() call that spans an .await can 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions