#remote-procedure #proto #rpc #capnp #protocols

capnp-rpc

implementation of the Cap'n Proto remote procedure call protocol

87 releases

0.20.2 Sep 22, 2024
0.19.5 Sep 9, 2024
0.19.2 Jun 1, 2024
0.19.0 Jan 14, 2024
0.0.3 Nov 23, 2014

#43 in Network programming

Download history 21140/week @ 2024-08-21 19320/week @ 2024-08-28 25523/week @ 2024-09-04 21732/week @ 2024-09-11 27202/week @ 2024-09-18 23084/week @ 2024-09-25 26056/week @ 2024-10-02 22304/week @ 2024-10-09 26153/week @ 2024-10-16 26110/week @ 2024-10-23 25738/week @ 2024-10-30 31809/week @ 2024-11-06 34499/week @ 2024-11-13 34236/week @ 2024-11-20 28523/week @ 2024-11-27 34861/week @ 2024-12-04

139,879 downloads per month
Used in 40 crates (16 directly)

MIT license

2MB
43K SLoC

capnp-rpc-rust

crates.io

documentation

This is a level one implementation of the Cap'n Proto remote procedure call protocol. It is a fairly direct translation of the original C++ implementation.

Defining an interface

First, make sure that the capnp executable is installed on your system, and that you have the capnpc crate in the build-dependencies section of your Cargo.toml. Then, in a file named foo.capnp, define your interface:

@0xa7ed6c5c8a98ca40;

interface Bar {
    baz @0 (x :Int32) -> (y :Int32);
}

interface Qux {
    quux @0 (bar :Bar) -> (y :Int32);
}

Now you can invoke the schema compiler in a build.rs file, like this:

fn main() {
    ::capnpc::CompilerCommand::new().file("foo.capnp").run().unwrap();
}

and you can include the generated code in your project like this:

pub mod foo_capnp {
  include!(concat!(env!("OUT_DIR"), "/foo_capnp.rs"));
}

Calling methods on an RPC object

For each defined interface, the generated code includes a Client struct that can be used to call the interface's methods. For example, the following code calls the Bar.baz() method:

fn call_bar(client: ::foo_capnp::bar::Client)
   -> Box<Future<Item=i32, Error=::capnp::Error>>
{
    let mut req = client.baz_request();
    req.get().set_x(11);
    Box::new(req.send().promise.and_then(|response| {
         Ok(response.get()?.get_y())
    }))
}

A bar::Client is a reference to a possibly-remote Bar object. The Cap'n Proto RPC runtime tracks the number of such references that are live at any given time and automatically drops the object when none are left.

Implementing an interface

The generated code also includes a Server trait for each of your interfaces. To create an RPC-enabled object, you must implement that trait.

struct MyBar {}

impl ::foo_capnp::bar::Server for MyBar {
     fn baz(&mut self,
            params: ::foo_capnp::bar::BazParams,
            mut results: ::foo_capnp::bar::BazResults)
        -> Promise<(), ::capnp::Error>
     {
         // `pry!` is defined in capnp_rpc. It's analogous to `try!`.
         results.get().set_y(pry!(params.get()).get_x() + 1);

         Promise::ok(())
     }
}

Then you can convert your object into a capability client like this:

let client: foo_capnp::bar::Client = capnp_rpc::new_client(MyBar {});

This new client can now be sent across the network. You can use it as the bootstrap capability when you construct an RpcSystem, and you can pass it in RPC method arguments and results.

Async methods

The methods of the generated Server traits return a value of type Promise<(), ::capnp::Error>. A Promise is either an immediate value, constructed by Promise::ok() or Promise::err(), or it is a wrapper of a Future, constructed by Promise::from_future(). The results will be sent back to the method's caller once two things have happened:

  1. The Results struct has been dropped.
  2. The returned Promise has resolved.

Usually (1) happens before (2).

Here's an example of a method implementation that does not return immediately:

struct MyQux {}

impl ::foo_capnp::qux::Server for MyQux {
     fn quux(&mut self,
             params: ::foo_capnp::qux::QuuxParams,
             mut results: ::foo_capnp::wux::QuuxResults)
        -> Promise<(), ::capnp::Error>
     {
         // Call `baz()` on the passed-in client.

         let bar_client = pry!(pry!(params.get()).get_bar());
         let mut req = bar_client.baz_request();
         req.get().set_x(42);
         Promise::from_future(req.send().promise.and_then(move |response| {
             results.get().set_y(response.get()?.get_y());
             Ok(())
         }))
     }
}

It's possible for multiple calls of quux() to be active at the same time on the same object, and they do not need to return in the same order as they were called.

Further reading

Dependencies

~0.8–1.1MB
~20K SLoC