Skip to content

Method handler codegen api#2538

Open
sauravzg wants to merge 3 commits intohyperium:masterfrom
sauravzg:method_handler_codegen_api
Open

Method handler codegen api#2538
sauravzg wants to merge 3 commits intohyperium:masterfrom
sauravzg:method_handler_codegen_api

Conversation

@sauravzg
Copy link
Collaborator

@sauravzg sauravzg commented Mar 6, 2026

Add Public handler API traits and underlying core underlying public structures and APIs

Summary

This pull request introduces the foundational API traits, Push Stream model, and Protobuf view abstractions to establish the primary interfaces for the new generic gRPC server architecture. It eliminates hard Protobuf dependencies, resolves GAT reborrowing constraints, and introduces a more performant streaming execution model.

Key Architectural Changes

1. Push-Based Stream API (PushStream)

Replaces the standard pull-based streaming model with a C++ inspired, push-based (PushStream) API for server responses. This is the most significant structural change, designed to resolve limitations in the current model:

  • Execution Model: Unifying the asynchronous execution context strictly enforces the gRPC requirement of receiving messages followed by a terminal error and native short circuiting of streams at compile team eliminating the need for manual state machines to ensure order of messages , errors and empty objects.
  • Generics over Dynamic Dispatch: Replaces dyn APIs (e.g., RecvStream) with generics. This enables nightly specialization, offering power users a performant workaround for Rust's limited native RTTI without relying on custom vtables.
  • Performance: This is very minor and will more often than not be made redundant by complex transformations. Removes the constant overhead of wrapping and unwrapping yielded items in Result<Message, Error>, directly streamlining the message delivery pipeline.
  • Future-Proofing: The core traits are explicitly encapsulated within struct wrappers. This grants the gRPC layer the flexibility to transparently introduce abstractions, such as task-local management, without violating public API stability.
  • Known Limitation: The consumer API is intentionally stateless to minimize overhead. Consequently, consumers are not explicitly made aware of stream termination, which currently precludes a zero-cost mechanism for asynchronous cleanups when the stream concludes.

2. Server-Side Method Traits for Codegen

Introduces UnaryMethod, ClientStreamingMethod, ServerStreamingMethod, and BidiStreamingMethod as the definitive interfaces for code generation.

  • Handlers are modeled strictly as asynchronous functions with a (request_stream, response_sink) signature.
  • Access is scoped exclusively to message payloads. Header/trailer mutation is explicitly delegated to interceptor APIs, and unary request reference copying is required for task spawning.

3. Message View Abstractions (AsView & AsMut)

Decouples the core implementation from the protobuf crate by abstracting standard references and Protobuf views.

  • Resolves complex HRTB errors originating from Generic Associated Type (GAT) reborrowing limitations.
  • Introduces a static operator pattern (AsMut::reborrow_view<'a, 'b>(view: &'b mut Self::Mut<'a>) -> Self::Mut<'b>) directly bound to the provider trait, successfully allowing mutable view reborrowing without consuming the original reference.

sauravzg added 3 commits March 6, 2026 13:40
These traits are supposed to provide abstractions over standard references and protobuf views and muts to eliminate hard dependency on protobuf

The biggest challenge in the message module is to solve the long-standing difficulty of reborrowing mutable message views.

The Problem: Reborrowing GATs
When working with Generic Associated Types (GATs) for mutable views (e.g., type Mut<'a>), we frequently need to "reborrow" a view to pass it to a child function without consuming the original. However, traditional approaches fail here:
1. Foreign Types: We cannot add a .reborrow() method directly to protobuf::Mut as it is a foreign type.
2. HRTB Hell: Defining a separate Reborrow trait and requiring for<'a> T::Mut<'a>: Reborrow leads to complex Higher-Ranked Trait Bound errors (e.g., "implementation is not general enough") that are extremely difficult to satisfy in Rust's current type system.
The Solution: Static Operator Pattern
We bypass these issues by moving the reborrowing logic into the provider trait itself as a static operator.
* Introduced AsMut::reborrow_view<'a, 'b>(view: &'b mut Self::Mut<'a>) -> Self::Mut<'b>.
* This bundles the reborrowing logic with the type definition, avoiding the need for extra traits or complex HRTB bounds on the view type itself.
Changes:
* as_mut.rs: Defined AsMut with the reborrow_view static operator. Implemented it for protobuf::Message and added tests verifying that views can be reborrowed and mutated without consuming the original.
* as_view.rs: Defined AsView for immutable views.
This introduces a push-based stream API (`PushStream`) for gRPC server responses, modeled after internal C++ patterns to address limitations in the current pull-based model.

Key architectural decisions:
* **Push vs. Pull:** Standard streams yield `Result<Message, Error>`, but gRPC strictly requires messages followed by a terminal error. A unified async execution context also prevents partial consumption bugs and eliminates the performance overhead of constantly wrapping/unwrapping items in a `Result`.
* **Generics over Dynamic Dispatch:** Generics are used instead of `dyn` APIs (like `RecvStream`) to enable nightly specialization. This provides power users with a workaround for Rust's limited native RTTI without requiring custom vtables.
* **Struct Wrappers over Raw Traits:** The underlying traits are encapsulated in struct wrappers to future-proof the API. This gives the gRPC layer the flexibility to transparently manage features like task locals later without breaking public stability.

Known Pitfalls:
* **Stateless Consumer API:** The consumer API is completely stateless and is never explicitly made aware of the end of the stream. Consequently, there is currently no zero-cost or trivial way to implement asynchronous cleanups when the stream concludes.
Introduce the public API traits for server-side gRPC methods (`UnaryMethod`, `ClientStreamingMethod`, `ServerStreamingMethod`, `BidiStreamingMethod`).
These traits are designed to be the primary interface for codegen.

Currently, the API models a handler as an async function with a (requeststream, responsesink) interface.

Note on design:
- The public API is scoped to access request and response messages only.
- Modifications to headers and trailers are intended to be handled via "interceptor" APIs, following the Java gRPC model.
- Contextual information is expected to be accessed via a read-only local context.

Known pitfalls:
- Unary inputs are references, implying that they will not trivially work with spawns. While responses can be shallow copied due to them being mut refs, requests will have to be copied if they need to be sent to a different task.
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.

1 participant