Goal: seamless RPC between Rust programs
Remoc treats an RPC connection as an ongoing typed relationship between two Rust programs, not only a sequence of requests and responses. Traits, ordinary Rust data, channels and remote objects can all cross the same connection.
Rust types are the contract
Traits become remotely callable with
#[rtc::remote].
Arguments and results are Serde types, so there is no separate interface language
or generated source to keep alongside them.
Channels are transferable values
Typed channel endpoints can travel inside messages and calls, then continue carrying values after the original call returns. They can be created dynamically, sent in either direction and nested inside other channels.
Either end can serve
The peer that opens a connection can also send back a channel or remote trait client. A device that connects through NAT can therefore expose an interface over the same connection.
One connection carries everything
Channels, calls and objects share one byte or packet stream. Each has independent flow control, so a receiver that stops reading stalls its own sender rather than unrelated traffic.
Feature comparison
The table compares each library against that goal. Green means built in, amber means constraints or extra setup, and red means no documented built-in support.
| Remoc | tarpc | gRPC (tonic) | Cap'n Proto | |
|---|---|---|---|---|
| Interface and contract | ||||
| Contract The source from which both peers derive the service methods and message shapes. | yesRust traits and types | yesRust traits | partly.proto schema |
partly.capnp schema |
| Interface is authored as a Rust trait Whether you write the RPC API directly as a Rust trait rather than in a separate schema language. | yes#[rtc::remote] |
yes | nogenerated from the schema | nogenerated from the schema |
| Messages use your existing Rust types Whether arguments and results can use normal Serde Rust types instead of generated message structs. | yes | yes | nogenerated prost messages | nogenerated reader and builder types |
| No code-generation build script Whether the usual setup avoids generating Rust source from schema files during the build. | yes | yes | noprotoc and prost-build |
nocapnp compiler |
| Non-Rust clients Whether supported implementations allow a peer to be written in another programming language. | no | no | yes | yes |
| Runs in a web browser Whether the library provides a supported way to make its RPC calls from within a web browser. | yesWebAssembly, full API | nonot documented | partlygrpc-web: unary and server streaming only | nonot documented |
| Transferable channels, objects and functions | ||||
| Hands over a live reference the peer keeps using Whether a call can pass a handle to a remote service or object that the recipient invokes after the original call finishes. | yesremote trait client or remote object | no | no | yescapability reference |
| Dependent calls in one round trip Whether a method can be called on a remote value returned by another call before the first result arrives, also known as promise pipelining. | yespromise pipelining | no | no | yespromise pipelining |
| Live references need no schema declaration Whether channel and object handles can be transferred without declaring their interfaces in an external schema. | yes | — | — | no declared in the schema |
| Typed channels as message content Whether arguments and results can contain typed channel endpoints that continue carrying values after the call. | yesremote channels | no | no | nocapability interfaces can model streams |
| Callbacks as values Whether a call can pass behavior that the recipient invokes later, allowing it to call back the sender. | yesremote functions and closures | nonot transferable in calls | nobidirectional streams, not callable values | yesschema-declared capabilities |
| Shape of the conversation | ||||
| Either peer can serve over one connection Whether the connection initiator and acceptor can both expose callable APIs over that same connection. | yessend back a trait client | no | nobidirectional streams, not reverse calls | yescallback capabilities |
| Server pushes without the client polling Whether a server can send updates over an established communication path without waiting for another client request. | yestransferred channel or callback | no | yesserver streaming | yescallback capability |
| Streaming model How the library represents a sequence of values: transferable channels, method-declared streams or application-defined interfaces. | yestransferable mpsc, watch and broadcast channels | nonot built in | yesmethod-declared client, server or bidirectional streams | partlycapability interfaces; -> stream adds flow control |
| Remotely observable collections Whether built-in collection types can notify a remote peer when their entries change. | yesobservable collections | no | no | no |
| Connection and transport | ||||
| Transport flexibility Whether the RPC layer can run over application-chosen transports instead of requiring a specific network protocol. | yesany byte or packet stream | yesany byte stream | partlyrequires HTTP/2 framing | yesany byte stream |
| Concurrent calls share one connection Whether many calls can be in flight at once over one underlying connection. | yesframe-multiplexed channels and calls | yesrequest IDs over one shared queue | yesone HTTP/2 stream per call | yesquestion IDs over one connection |
| A blocked receiver stalls only its own traffic Whether backpressure from a receiver that stops reading is confined to its own traffic instead of delaying unrelated calls. | yescredit window per channel | noone shared transport queue | yesper-stream window; connection window also shared | partlyonly for -> stream methods; ordinary calls have none |
| Compatibility across versions | ||||
| Add a field Whether old and new programs can still exchange messages after a field is added. | yesanywhere; missing fields take a default | partlydepends on the codec | yesuse a new field number | yesuse the next ordinal |
| Remove a field Whether old and new programs can still exchange messages after a field is removed. | yesanywhere; old readers need a default | partlydepends on the codec | yesreserve its field number | nonot documented as safe |
| Rename a field Whether a field's source-code name can change without changing its identity on the wire. | partlywith a stable numbered identifier | partlydepends on the codec | yeskeep its field number | yeskeep its ordinal |
| Reorder fields in source Whether field declarations can move to a different order without changing the wire format. | yesPostbag Full matches field identifiers | partlydepends on the codec | yeskeep field numbers | yeskeep ordinals |
| Add an enum variant Whether an enum can gain a variant while older programs continue to read messages safely. | yesolder readers need a fallback variant | partlydepends on the codec | yesuse a new numeric value | yesuse the next ordinal; older readers handle unknown values |
| Recover an incompatible field Whether a known field whose encoded content no longer matches its type can take a default without discarding the rest of the message. | yesopt in per field; surrounding data is preserved | nonot provided by bundled codecs | noa bad known field aborts message decoding | partlypointer fields are read separately; the caller handles each error |
| Operations and infrastructure | ||||
| Deadlines and cancellation Whether a caller can bound work by time and tell the server to stop calls that are no longer needed. | yesa Tokio timeout cancels the whole call tree | yesdeadline on every request | yesgrpc-timeout header |
partlycancellation only |
| Built-in trace-context propagation Whether distributed tracing identity travels with calls so spans can be connected across peers. | yesOpenTelemetry context with each call | yesOpenTelemetry context with each call | partlyvia metadata and an interceptor | nopass it explicitly |
| HTTP-aware proxies and service meshes Whether standard HTTP infrastructure can inspect individual RPC methods for routing, metrics and rate limits. | noconnection is opaque | nonot HTTP-based | yesper-method routing, metrics and limits | nonot HTTP-based |
| Choosing a library | ||||
| Use it when The scenario that most directly matches each library's design strengths. | Rust-to-Rust RPC with evolvable types, from simple calls to channels and remote objects | request/response RPC between Rust programs, with plain values in and out | services cross languages or ride HTTP infrastructure | capability RPC and pipelining must work across languages |
The table compares documented, built-in capabilities as of September 2026.
Remoc's version-compatibility rows assume its default
Postbag Full codec.
The compared libraries
tarpc
tarpc is the closest alternative: it also defines services as Rust traits without a separate schema, uses Serde for the messages and sends the trace context with each call. It stays with request/response RPC: a call takes values and returns a value.
In Remoc, a call may also carry a channel half, a callback or a remote object, so a method can return a stream of updates, return another remote object or report progress while it runs, and with the Postbag codec the two endpoints may run different versions of the types.
gRPC with tonic
tonic implements gRPC for Rust.
A .proto schema defines the service and generates code for each
language. Calls run over HTTP/2, which makes gRPC a good match for cross-language
APIs and infrastructure that routes, measures or limits individual calls.
Remoc instead keeps Rust types as the contract: there is no schema and no code generator, and any Serde type, including enums carrying data and generics, is used directly. It multiplexes its own channels over the supplied transport, so a call can return a stream in either direction and a channel can be sent along with any value, not only as the result of a streaming method.
Volo (gRPC and Thrift)
Volo is a high-performance Rust microservice framework with gRPC and Thrift implementations, generated interfaces, middleware, and extension points for service discovery and load balancing. Its gRPC mode broadly follows the communication model shown in the gRPC column above: calls exchange schema-defined values and method-declared streams rather than transferable Rust channels, callbacks or remote objects. Volo is a good fit for polyglot microservices and conventional service infrastructure.
Remoc is the stronger fit when both endpoints are Rust and calls need to pass typed channels, callbacks or remote objects that remain usable after the call returns. These can be created dynamically, sent in either direction and multiplexed over a single application-provided connection.
Cap'n Proto
Cap'n Proto RPC also lets references to callable objects travel in messages and supports pipelining dependent calls. Its schema and capability model work across supported languages.
Remoc supports similar patterns using Rust traits and channel endpoints, including pipelined trait calls, with ordinary Rust types in place of generated readers and builders, but is limited to Rust peers.
See whether Remoc fits your application
Start with an RPC example, read the RPC documentation, or explore the complete API.