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!
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:
Rust users accustomed to async/await found it annoying
to need to manually construct a Promise like this.
The ? operator did not work inside such a method body without enabling the unstable try_trait_v2 feature.
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.
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!
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.