0.22 — async methods

27 Oct 2025

Today I have published version 0.22.0 of the capnp, capnpc, capnp-futures, and capnp-rpc, crates.

The new release enables idiomatic usage of async and await in RPC method implementations, taking advantage of the “return position impl Trait in traits” feature stabilized in Rust 1.75. The relevant changes landed in pull request #593. Thank you @luisholanda for the contribution!

how it was before

Suppose we had a capnp schema file example.capnp declaring the following interface:

interface Foo {
  bar @0 (x :UInt16) -> (y :UInt16);
}

In previous versions, capnpc-rust would generate a Rust trait with the following signature:

pub mod foo {
  trait Server {
    fn bar(&mut self, foo::BarParams, foo::BarResults) -> Promise<(), Error>;
  }
  // ...
}

The return type Promise<(), Error> allowed implementations of the method to use asynchronous code, like this:

Promise::from_future(async move {
  // ... do some asynchronous stuff here
})

However, there were some problems with this setup:

  1. Rust users accustomed to async/await found it annoying to need to manually construct a Promise like this.

  2. The ? operator did not work inside such a method body without enabling the unstable try_trait_v2 feature.

  3. There was no way to get access to the borrowed &mut self parameter from within the async context, because Promise::from_future() requires that its input be 'static.

the new way

Starting in version 0.22.0, the generated trait has the following signature:

pub mod foo {
  trait Server {
    fn bar(&self,
           foo::BarParams,
           foo::BarResults) -> impl Future<Output=Result<(), Error>> + '_;
  }
  // ...
}

Now user-provided implementations can use the async fn syntax like this:


impl foo::Server for FooImpl {
  async fn bar(&self, foo::BarParams, foo::BarResults) -> Result<(), Error> {
    // ... do some asynchronous stuff here, possibly using the `?` operator,
    // and possibly using &self after an await point.
  }
}

This solves all three of the problems noted above!

the price of progress

This is a breaking change. All implementations of RPC traits will need to be updated to account for the change in method signature.

In particular, because the &mut self parameter has changed to an immutable &self parameter, RPC objects may need to add their own interior mutability. This can typically be done by wrapping data in Cell<T> or RefCell<T>. This commit to the Sandstorm Collections app is an example of what such an update might look like.

Moreover, getting this all to work required adding some async/await in the generated method dispatch code. Therefore, capnpc-rust no longer supports generating code for Rust editions 2015 and 2018.

-- posted by dwrensha

capnproto-rust on github
more posts