Skip to content

grpc: implement new proto generated code API#2549

Merged
dfawley merged 3 commits intohyperium:masterfrom
dfawley:codegen
Mar 24, 2026
Merged

grpc: implement new proto generated code API#2549
dfawley merged 3 commits intohyperium:masterfrom
dfawley:codegen

Conversation

@dfawley
Copy link
Copy Markdown
Collaborator

@dfawley dfawley commented Mar 12, 2026

  • Rename tonic-protobuf-build to grpc-protobuf-build
  • Change protoc-gen-rust-grpc to produce the new generated code API
  • Create grpc-protobuf crate for holding shared proto implementation
  • Fix handling of trailers in tonic transport implementation
  • Update interop's client_protobuf to use the new API

And:

@dfawley
Copy link
Copy Markdown
Collaborator Author

dfawley commented Mar 12, 2026

Generated Protobuf API

The primary goal of the gRPC library is to provide a natural and easy-to-use API for developers while also providing the highest possible performance through zero-cost abstractions, zero-copy design patterns, support for arenas, etc. The following API is designed to be ergonomic, using all native async Rust code usable with any async runtime, and with full support for arena allocated request and response messages.

Design Goals

  1. Separation of Data and Metadata: The generated methods accept and return RPC Messages and final Status only. Headers and Trailers are side-channel data. To access or modify them, users must explicitly use Interceptors. Removing gRPC-specific concepts from the generated API keeps the common path clean and type-safe, and allows us to provide the most reusable RPC API possible to minimize the cost of changing or supporting multiple RPC systems in the same application.

  2. Builder Pattern: RPCs are performed following the async builder pattern. Builders allow for configuration (timeouts, etc) and the optional use of arena-allocated messages at the call site (for unary and server-streaming calls).

  3. "Standard" Streams: Even though Rust's futures_core traits are not stable, the Rust ecosystem widely uses these types and this style of stream. We will implement concrete types that behave similarly to Sink and Stream for ease of use. Adapters may be provided to allow direct use of the futures_core features with our type (which must necessarily remain unstable since futures_core itself is unstable).

Target API

The following shows the generated API for the four basic call types, simplified somewhat to allow the basic structure to be understood.

pub fn unary(&self, req: impl AsView<Proxied = MyRequest>) -> UnaryCallBuilder<>
pub fn bidi(&self) -> BidiCallBuilder<>
pub fn server_stream(&self, req: impl AsView<Proxied = MyRequest>) -> ServerStreamBuilder<>
pub fn client_stream(&self) -> ClientStreamBuilder<>

The Builder types implement IntoFuture with an appropriate output. Unary and server streaming types will also have an additional async fn that allows the application to dispatch the call with a mutable response message view (&mut response_msg_mut) as an input parameter; these functions output Status*.

* - Note that we will not initially be using absl::Status since it is not available yet. We intend to swap to it when it becomes available.

Usage Examples

Below are examples of how application code will look when using our generated API. Inferred types are shown for completeness.

Unary Calls

// Simple usage with owned types
let res: Result<MyResponse, Status> =
    client.unary_call(proto!(MyRequest{ query: 1 })).await;
// Advanced usage: Arena-allocated request and response.
let mut request_view: MyRequestView<'_> = proto!(MyRequest{ }).as_view();
let mut response_view: MyResponseMut<'_> = MyResponse::new(); // Or obtain from an arena

let status: Status =
    client.unary_call(request_view)
        .with_timeout(Duration::from_secs(2))
        .with_response_message(&mut response_view)
        .await;

Bidi Streaming

let (tx: GrpcStreamingRequest<MyRequest, _>, rx: GrpcStreamingResponse<MyResponse, _>) =
      client.bidi_call(request_stream, response_sink).await;

tx.send_message(proto!(MyRequest { query: 1 })).await;
while let Some(msg) = rx.next().await {
    println!("received response message {msg:?}");
}

let status: Status = rx.status().await; // note: consumes rx

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, we're dropping support for generating tonic code. There may be some people using this plugin with Tonic in OSS. Do we want to keep Tonic code generation and add gRPC support with a flag to switch between the two?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking this would be unlikely because we never published a crate with it?

I can add it back if you think it's necessary, but I was thinking that we'd eventually have:

  1. tonic + prost codegen (existing)
  2. grpc + google-protobuf codegen (this PR)
  3. tonic + prost codegen that wraps a grpc channel / server instead of tonic.

Where (3) is the migration path for users that are currently using tonic+prost but want the new channel implementation ASAP, and both (1) and (3) are left at v0 and maintained as best-effort while (2) is released in our 1.0.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of a few issues raised with the protobuf team by users of Tonic's protobuf codegen.

I don't have enough context on the current state of prost or the best path forward for existing Tonic users. Perhaps @LucioFranco can weigh in on whether we can safely drop support for Tonic + Google protobuf.


If we do decide to drop it, we should delete the tonic-protobuf crate as well.

@dfawley
Copy link
Copy Markdown
Collaborator Author

dfawley commented Mar 13, 2026

cc @gu0keno0 @YutaoMa @ankurmittal as FYI - feel free to review if you have time, or just skim the design & look at the usage examples and the actual code in client_protobuf.rs, which was changed from a tonic-with-google-proto implementation into this API.

Comment thread grpc-protobuf-build/Cargo.toml Outdated
Comment thread grpc-protobuf/src/client/bidi.rs Outdated
Comment thread grpc-protobuf/src/client/bidi.rs Outdated
Comment thread grpc-protobuf/src/client/bidi.rs Outdated
Comment thread grpc-protobuf/src/client/mod.rs Outdated
Comment thread grpc-protobuf/src/client/mod.rs Outdated
Comment thread grpc-protobuf/src/client/bidi.rs
Comment thread grpc-protobuf/src/client/client_streaming.rs Outdated
Comment thread grpc-protobuf/src/client/client_streaming.rs Outdated
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware of a few issues raised with the protobuf team by users of Tonic's protobuf codegen.

I don't have enough context on the current state of prost or the best path forward for existing Tonic users. Perhaps @LucioFranco can weigh in on whether we can safely drop support for Tonic + Google protobuf.


If we do decide to drop it, we should delete the tonic-protobuf crate as well.

Comment thread interop/src/bin/client.rs Outdated
@arjan-bal arjan-bal assigned dfawley and unassigned arjan-bal Mar 16, 2026
Copy link
Copy Markdown
Collaborator Author

@dfawley dfawley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the review; all comments addressed

Comment thread grpc-protobuf-build/Cargo.toml Outdated
Comment thread grpc-protobuf/src/client/bidi.rs
Comment thread grpc-protobuf/src/client/bidi.rs Outdated
Comment thread grpc-protobuf/src/client/bidi.rs Outdated
Comment thread grpc-protobuf/src/client/client_streaming.rs Outdated
Comment thread grpc-protobuf/src/client/bidi.rs Outdated
Comment thread grpc-protobuf/src/client/client_streaming.rs Outdated
Comment thread grpc-protobuf/src/client/mod.rs Outdated
Comment thread grpc-protobuf/src/client/mod.rs Outdated
Comment thread interop/src/bin/client.rs Outdated
@dfawley
Copy link
Copy Markdown
Collaborator Author

dfawley commented Mar 16, 2026

Thanks for the review; all comments addressed

Aaaand immediately after hitting send, I realized I didn't add all of the copyrights. It should be good now though.

Comment thread grpc-protobuf-build/Cargo.toml
@dfawley dfawley assigned arjan-bal and unassigned dfawley Mar 18, 2026
@dfawley
Copy link
Copy Markdown
Collaborator Author

dfawley commented Mar 18, 2026

@arjan-bal I updated to follow the sealed pattern from https://predr.ag/blog/definitive-guide-to-sealed-traits-in-rust/#allowing-only-some-methods-to-be-called since the hidden trait doesn't prevent usage of methods.

Comment thread grpc-protobuf/src/client/mod.rs Outdated
Comment thread tonic-protobuf/Cargo.toml
Comment thread grpc-protobuf/src/lib.rs Outdated
Comment thread grpc-protobuf/src/client/mod.rs
Comment thread grpc-protobuf/src/client/mod.rs Outdated
Copy link
Copy Markdown
Collaborator

@arjan-bal arjan-bal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some minor comments, otherwise LGTM.

@arjan-bal arjan-bal assigned dfawley and unassigned arjan-bal Mar 20, 2026
Copy link
Copy Markdown
Collaborator

@arjan-bal arjan-bal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Comment thread grpc-protobuf/src/client/mod.rs
@dfawley
Copy link
Copy Markdown
Collaborator Author

dfawley commented Mar 23, 2026

I had to do a little work to make the interop tests work -- they needed to override the authority, so I plumbed that around. And I couldn't see the error that would have let me fix it quickly because PF wasn't setting a failing picker (I did that in a later PR, too), so I added that in here too. PTAL

@dfawley dfawley assigned arjan-bal and unassigned dfawley Mar 23, 2026
Copy link
Copy Markdown
Collaborator

@arjan-bal arjan-bal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

let runtime = self.runtime.clone();
// TODO: Implement Drop that cancels this task.
self.runtime.spawn(Box::pin(async move {
runtime.sleep(Duration::from_millis(200)).await;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This call to sleep seems to be some kind of a hack. Maybe we should remove it?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not as part of this change. This is a hacky PF policy -- the real one is in #2340.

sc: self.subchannel.as_ref().unwrap().clone(),
}),
});
} else if state.connectivity_state == ConnectivityState::TransientFailure {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please add a test for this change?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are currently no tests at all for this hack of the PF policy. I could add them but it would be short-lived and testing a known very-incomplete policy. Let's just keep limping like this until #2340 (or some form of it) has landed.

Comment thread grpc-protobuf/src/client/mod.rs
@arjan-bal arjan-bal assigned dfawley and unassigned arjan-bal Mar 24, 2026
- Rename tonic-protobuf-build to grpc-protobuf-build
- Change protoc-gen-rust-grpc to produce the new generated code API
- Create grpc-protobuf crate for holding shared proto implementation
- Fix handling of trailers in tonic transport implementation
- Add test_util module for sharing interceptor testing code
- Update interop's client_protobuf to use the new API
Comment thread grpc-protobuf/src/client/mod.rs
Comment thread grpc-protobuf/src/client/mod.rs Outdated
self.rebuild(|c| c.with_interceptor(interceptor), Internal)
}

/// Attaches `interceptor` to the call.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: same rustdoc as the above

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still accurate, despite being a copy. :)

I updated the comment to specify one is a multi-use interceptor and one is a single-use interceptor.

Comment thread grpc-protobuf/src/client/unary.rs Outdated
use crate::client::Internal;

/// Configures a unary call for gRPC Protobuf. Implements `IntoFuture` which
/// perfors the call and resolves to the response as a `Result<Res, Status>`.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: perform

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@dfawley dfawley merged commit b63ce7d into hyperium:master Mar 24, 2026
21 checks passed
@dfawley dfawley deleted the codegen branch March 24, 2026 21:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants