Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
Rust as first-class citizen for gRPC ecosystem
proto_rs makes Rust the source of truth for your Protobuf and gRPC definitions, providing 4 macros that will handle all proto-related work, so you don't need to touch .proto files at all.
Motivation
- I hate to do conversion after conversion for conversion
- I love to see Rust only as first-class citizen for all my stuff
- I hate bloat, so no protoc (shoutout to PewDiePie debloat trend)
- I don't want to touch .proto files at all
What can you build with proto_rs?
- Pure-Rust schema definitions. Use
#[proto_message],#[proto_rpc], and#[proto_dump]to declare every message and service in idiomatic Rust while the derive machinery keeps.protofiles in sync for external consumers. - Tailored encoding pipelines.
ProtoShadowlets you bolt custom serialization logic onto any message, opt into multiple domain "suns", and keep performance-sensitive conversions entirely under your control. - Zero-copy Tonic integration. Codegen support borrowed request/response wrappers, and
ToZeroCopy*traits so RPC handlers can run without cloning payloads. - Workspace-wide schema registries. The build-time inventory collects every emitted
.proto, making it easy to materialize or lint schemas from a single crate.
For fellow proto <-> native typeconversions enjoyers <=0.5.0 versions of this crate implement different approach
Key capabilities
- Message derivation –
#[proto_message]turns a Rust struct or enum into a fully featured Protobuf message, emitting the corresponding.protodefinition and implementingProtoExtso the type can be encoded/decoded without extra glue code. The generated codec now reuses internal helpers to avoid redundant buffering and unnecessary copies. - RPC generation –
#[proto_rpc]projects a Rust trait into a complete Tonic service and/or client. Service traits stay idiomatic while still interoperating with non-Rust consumers through the generated.protoartifacts, and the macro avoids needless boxing/casting in the conversion layer. - On-demand schema dumps –
#[proto_dump]andinject_proto_import!let you register standalone definitions or imports when you need to compose more complex schemas. - Workspace-wide schema registry – With the
build-schemasfeature enabled you can aggregate every proto that was emitted by your dependency tree and write it to disk viaproto_rs::schemas::write_all. The helper deduplicates inputs and writes canonical packages derived from the file path. - Opt-in
.protoemission – Proto files are written only when you ask for them via theemit-proto-filescargo feature or thePROTO_EMIT_FILE=1environment variable, making it easy to toggle between codegen and incremental development.
Define your messages and services using the derive macros with native rust types:
;
;
Yep, all types here are Rust types. We can then implement the server just like a normal Tonic service, and the .proto schema is generated for you whenever emission is enabled.
Advanced Features
Macros support all prost types, imports, skipping with default and custom functions, custom conversions, support for native Rust enums (like Status below) and prost enumerations (TestEnum in this example, see more in prosto_proto).
Struct with Advanced Attributes
Generated proto:
message Attr {
repeated string id_vec = 1;
optional string id_opt = 2;
Status status = 3;
optional Status status_opt = 4;
repeated Status status_vec = 5;
google.protobuf.Timestamp timestamp = 6;
repeated google.protobuf.Timestamp timestamp_vec = 7;
optional google.protobuf.Timestamp timestamp_opt = 8;
google.protobuf.TestEnum test_enum = 9;
optional google.protobuf.TestEnum test_enum_opt = 10;
repeated google.protobuf.TestEnum test_enum_vec = 11;
int64 updated_at = 12;
}
Complex Enums
Generated proto:
message VeryComplexProto {
oneof value {
VeryComplexProtoFirst first = 1;
Address second = 2;
VeryComplexProtoThird third = 3;
VeryComplexProtoRepeated repeated = 4;
VeryComplexProtoOption option = 5;
VeryComplexProtoAttr attr = 6;
}
}
message VeryComplexProtoFirst {}
message VeryComplexProtoThird {
uint64 id = 1;
Address address = 2;
}
message VeryComplexProtoRepeated {
repeated uint64 id = 1;
repeated Address address = 2;
}
message VeryComplexProtoOption {
optional uint64 id = 1;
optional Option address = 2;
}
message VeryComplexProtoAttr {
repeated string id_vec = 1;
optional string id_opt = 2;
Status status = 3;
optional Status status_opt = 4;
repeated Status status_vec = 5;
google.protobuf.Timestamp timestamp = 6;
repeated google.protobuf.Timestamp timestamp_vec = 7;
optional google.protobuf.Timestamp timestamp_opt = 8;
google.protobuf.TestEnum test_enum = 9;
optional google.protobuf.TestEnum test_enum_opt = 10;
repeated google.protobuf.TestEnum test_enum_vec = 11;
}
Inject Proto Imports
It's not mandatory to use this macro at all, macros above derive and inject imports from attributes automaticly
But in case you need it, to add custom imports to your generated .proto files use the inject_proto_import! macro:
inject_proto_import!;
This will inject the specified import statements into the target .proto file
Custom encode/decode pipelines with ProtoShadow
ProtoExt types pair with a companion Shadow type that implements ProtoShadow. This trait defines how a value is lowered into the bytes that will be sent over the wire and how it is rebuilt during decoding. The default derive covers most standard Rust types, but you can substitute a custom representation when you need to interoperate with an existing protocol or avoid lossy conversions. Default Shadow of type is &Self for complex types and Self for primitive
The fastnum decimal adapter shows how to map fastnum::D128 into a compact integer layout while still exposing ergonomic Rust APIs:
By hand-tuning from_sun and to_sun you can remove redundant allocations, hook into validation logic, or bridge Rust-only types into your RPC surface without ever touching .proto definitions directly.
When you need the same wire format to serve multiple domain models, supply several sun targets in one go:
Each sun entry generates a full ProtoExt implementation so the same shadow type can round-trip either domain struct without code duplication.
Zero-copy server responses
Service handlers produced by #[proto_rpc] work with ZeroCopyResponse to avoid cloning payloads. Any borrowed message (&T) can be turned into an owned response buffer via ToZeroCopyResponse::to_zero_copy, and the macro also supports infallible method signatures that return a response directly. The server example in examples/proto_gen_example.rs demonstrates both patterns:
This approach keeps the encoded bytes around without materializing a fresh GoonPong for each call. Compared to Prost-based services—where &T data must first be cloned into an owned T before encoding—ZeroCopyResponse removes at least one allocation and copy per RPC, which shows up as lower tail latencies for large payloads. The Criterion harness ships a dedicated bench_zero_copy_vs_clone group that regularly clocks the zero-copy flow at 1.3×–1.7× the throughput of the Prost clone-and-encode baseline, confirming the wins for read-heavy endpoints.
Zero-copy client requests
Clients get the same treatment through ZeroCopyRequest. The generated stubs accept any type that implements ProtoRequest, so you can hand them an owned message, a tonic::Request<T>, or a zero-copy wrapper created from an existing borrow:
let payload = RizzPing ;
let zero_copy = .to_zero_copy;
client.rizz_ping.await?;
If you already have a configured tonic::Request<&T>, call request.to_zero_copy() to preserve metadata while still avoiding a clone. The async tests in examples/proto_gen_example.rs and tests/rpc_integration.rs show how to mix borrowed and owned requests seamlessly, matching the server-side savings when round-tripping large messages.
Performance trade-offs vs. Prost
The runtime exposes both zero-copy and owned-code paths so you can pick the trade-off that matches your workload. Wrapping payloads in ZeroCopyRequest/ZeroCopyResponse means the encoder works with borrowed data (SunByRef) and never materializes owned clones before writing bytes to the socket, which is why the benchmark suite records 70% higher throughput than prost::Message when measuring identical service implementations (bench_zero_copy_vs_clone).
Owned encoding\decoding is somewhat slower or faster in somecases - see bench section
Collecting schemas across a workspace
Enable the build-schemas feature for the crate that should aggregate .proto files and call the helper at build or runtime:
This walks the inventory of registered schemas and writes deduplicated .proto files with a canonical header and package name derived from the file path.
Controlling .proto emission
proto_rs will only touch the filesystem when one of the following is set:
- Enable the
emit-proto-filescargo feature to always write generated files. - Set
PROTO_EMIT_FILE=1(ortrue) to override the default at runtime. - Set
PROTO_EMIT_FILE=0(orfalse) to force emission off even if the feature is enabled.
The emission logic is shared by all macros so you can switch behaviours without code changes.
Examples and tests
Explore the examples/ directory and the integration tests under tests/ for end-to-end usage patterns, including schema-only builds and cross-compatibility checks.
To validate changes locally run:
The test suite exercises more than 400 codec and integration scenarios to ensure the derived implementations stay compatible with Prost and Tonic.
Benchmarks
The repository bundles a standalone Criterion harness under benches/bench_runner. Run the benches with:
Each run appends a markdown report to benches/bench.md, including the bench_zero_copy_vs_clone comparison and encode/decode micro-benchmarks that pit proto_rs against Prost. Use those numbers to confirm zero-copy changes stay ahead of the Prost baseline and to track regressions on the clone-heavy paths.
Optional features
std(default) – pulls in the Tonic dependency tree and enables the RPC helpers.tonic(default) – compiles the gRPC integration layer, including the drop-in codecs, zero-copy request/response wrappers, and Tonic service/client generators.build-schemas– register generated schemas at compile time so they can be written later.emit-proto-files– eagerly write.protofiles during compilation.stable– compile everything on the stable toolchain by boxing async state. See below for trade-offs.fastnum,solana– enable extra type support.
Stable vs. nightly builds
The crate defaults to the nightly toolchain so it can use impl Trait in associated types for zero-cost futures when deriving RPC services. If you need to stay on stable Rust, enable the stable feature. Doing so switches the generated service code to heap-allocate and pin boxed futures, which keeps the API identical but introduces one allocation per RPC invocation and a small amount of dynamic dispatch. Disable the feature when you can use nightly to get the leanest possible generated code.
For the full API surface and macro documentation see docs.rs/proto_rs.