Try out gin-tonic - prost alternative

⚓ Rust    📅 2026-05-17    👤 surdeus    👁️ 2      

surdeus

crates.io

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)
  • A code generator replacing prost-build
  • A tonic codec implementation
  • A wrapper for 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:

message Foo {
  string my_uuid = 1;
}

This generates:

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:

import "gin/proto/gin.proto";

message Foo {
  string my_uuid = 1 [(gin_tonic.v1.rust_type) = "uuid::Uuid"];
}

The gin-tonic code generator produces:

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:

Feature Wire type Notes
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:

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.0944 µs 1.0957 µs 1.0974 µs]
gin_decode    time:   [2.4878 µs 2.4886 µs 2.4893 µs]

prost (including From conversions to idiomatic Rust types):

prost_encode  time:   [2.3426 µs 2.3477 µs 2.3568 µs]
prost_decode  time:   [2.7674 µs 2.7731 µs 2.7772 µs]

Decode performance is slightly better while encoding is about twice as fast.

Crates

Crate Description
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)

1 post - 1 participant

Read full topic

🏷️ Rust_feed