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.