#protobuf #derive #protocols

macro proto_convert_derive

Automatically derive Protobuf and Rust conversions

12 releases

new 0.1.11 Mar 9, 2025
0.1.10 Mar 9, 2025

#1027 in Development tools

Download history 1078/week @ 2025-03-04

1,078 downloads per month

Apache-2.0

27KB
404 lines

proto_convert_derive

Automatically derive conversions between Protobuf-compiled prost types and your native Rust types.

Overview

proto_convert_derive is a procedural macro for bidirectional conversions between Protobuf-generated types (prost) and Rust structs. This reduces boilerplate and handles proto3's lack of required fields (which result in Option and lots .expect or if let Some in your code. This macro simply .expects types.

Key Features

  • Automatically implements From<Proto> for Rust types and vice versa.
  • Supports collections like Vec<Proto>
  • Direct mapping for primitive types.
  • Unwraps optional fields with .expect.
  • Supports newtype wrappers.
  • Customizable Protobuf module (default is proto via #[proto(module = "your_module")]).
  • Ignore individual fields #[proto(ignore)]

Usage

Head to the examples.


lib.rs:

proto_convert_derive

Derive seamless conversions between prost-generated Protobuf types and custom Rust types.

Overview

proto_convert_derive is a procedural macro for automatically deriving efficient, bidirectional conversions between Protobuf types generated by prost and your native Rust structs. This macro will significantly reduce boilerplate when you're working with Protobufs.

Features

  • Automatic Bidirectional Conversion: Derives From<Proto> and Into<Proto> implementations.
  • Primitive Type Support: Direct mapping for Rust primitive types (u32, i64, String, etc.).
  • Option and Collections: Supports optional fields (Option<T>) and collections (Vec<T>).
  • Newtype Wrappers: Transparent conversions for single-field tuple structs.
  • Field Renaming: Customize mapping between Rust and Protobuf field names using #[proto(rename = "...")].
  • Custom Conversion Functions: Handle complex scenarios with user-defined functions via #[proto(derive_from_with = "...")] and #[proto(derive_into_with = "...")].
  • Ignored Fields: Exclude fields from conversion using #[proto(ignore)].
  • Configurable Protobuf Module: Defaults to searching for types in a proto module, customizable per struct or globally.

Usage

Given Protobuf definitions compiled with prost:

syntax = "proto3";
package service;

message Track {
    uint64 track_id = 1;
}

message State {
    repeated Track tracks = 1;
}

Derive conversions in Rust:

use proto_convert_derive::ProtoConvert;
mod proto {
    tonic::include_proto!("service");
}

#[derive(ProtoConvert)]
#[proto(module = "proto")]
pub struct Track {
    #[proto(transparent, rename = "track_id")]
    pub id: TrackId,
}

#[derive(ProtoConvert)]
pub struct TrackId(u64);

#[derive(ProtoConvert)]
pub struct State {
    pub tracks: Vec<Track>,
}

Complex conversions, akin to serde(deserialize_with = "..")

use std::collections::HashMap;

#[derive(ProtoConvert)]
#[proto(rename = "State")]
pub struct StateMap {
    #[proto(derive_from_with = "into_map", derive_into_with = "from_map")]
    pub tracks: HashMap<TrackId, Track>,
}

pub fn into_map(tracks: Vec<proto::Track>) -> HashMap<TrackId, Track> {
    tracks.into_iter().map(|t| (TrackId(t.track_id), t.into())).collect()
}

pub fn from_map(tracks: HashMap<TrackId, Track>) -> Vec<proto::Track> {
    tracks.into_values().map(Into::into).collect()
}

Ignoring fields:

use std::sync::atomic::AtomicU64;

#[derive(ProtoConvert)]
#[proto(rename = "State")]
pub struct ComplexState {
    pub tracks: Vec<Track>,
    #[proto(ignore)]
    pub counter: AtomicU64,
}

Limitations

  • Does not support enums or unions yet.
  • Assumes Protobuf-generated types live in a single module.
  • Optional Protobuf message fields (optional) use .expect and panic if missing; handle accordingly.

Dependencies

~195–620KB
~15K SLoC