
# gin-tonic
`gin-tonic` is a Rust protobuf library that lets you use your own types directly on the wire — no manual conversion boilerplate.
It provides:
- Protobuf serialization and deserialization (like [`prost`](https://docs.rs/prost))
- A code generator replacing [`prost-build`](https://docs.rs/prost-build)
- A [`tonic`](https://docs.rs/tonic) codec implementation
- A wrapper for [`tonic-build`](https://docs.rs/tonic-build) with extra features
- A `Scalar` trait to map any Rust type directly to a protobuf wire type
## The problem with other libraries
When you use a UUID in a protobuf message with `prost`, you write:
```protobuf
message Foo {
string my_uuid = 1;
}
```
This generates:
```rust
struct Foo {
my_uuid: String,
}
```
Your code wants `uuid::Uuid`, so you end up writing conversions everywhere — and handling parse errors at every call site.
## The gin-tonic approach
Annotate your `.proto` file with the Rust type you want:
```protobuf
import "gin/proto/gin.proto";
message Foo {
string my_uuid = 1 [(gin_tonic.v1.rust_type) = "uuid::Uuid"];
}
```
The `gin-tonic` code generator produces:
```rust
struct Foo {
my_uuid: uuid::Uuid,
}
```
The conversion is handled once, inside the `Scalar` trait implementation — not scattered across your codebase.
## Built-in UUID support
Two feature flags cover the UUID case out of the box:
| `uuid_string` | `string` | Parse errors handled in the wire type conversion |
| `uuid_bytes` | `bytes` | No parse errors; 16-byte fixed representation |
## Custom types
Implement `Scalar` for any type to use it as a protobuf field:
```rust
impl gin_tonic_core::Scalar<gin_tonic_core::scalars::ProtoString> for MyType {
const WIRE_TYPE: u8 = gin_tonic_core::WIRE_TYPE_LENGTH_ENCODED;
fn encode(&self, encoder: &mut impl gin_tonic_core::Encode) {
encoder.encode_str(&self.to_string());
}
fn decode(decoder: &mut impl gin_tonic_core::Decode) -> Result<Self, gin_tonic_core::ProtoError> {
decoder.decode_string()?.parse().map_err(Into::into)
}
}
```
## Benchmarks
Measured against prost 0.14.3 on an equivalent message with a UUID, 10 IP addresses, a string, and a nested map with 5 entries on AMD Ryzen AI 7 350.
**gin-tonic:**
```
gin_encode time: [1.3608 µs 1.3627 µs 1.3654 µs]
gin_decode time: [3.0161 µs 3.0198 µs 3.0238 µs]
```
**prost** (including `From` conversions to idiomatic Rust types):
```
prost_encode time: [2.4047 µs 2.4078 µs 2.4111 µs]
prost_decode time: [2.8447 µs 2.8497 µs 2.8559 µs]
```
Decode performance is roughly on par with prost while encoding is about 1.8× faster.
## Crates
| `gin-tonic` | Main crate — re-exports everything, includes code generator and tonic codec |
| `gin-tonic-core` | Runtime traits and serialization primitives |
| `gin-tonic-derive` | Derive macros (`Message`, `Enumeration`, `OneOf`) |