protto
Derive seamless conversions between prost-generated Protobuf types and custom Rust types.
Overview
protto 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>andInto<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
#[protto(proto_name = "...")]. - Custom Conversion Functions: Handle complex scenarios with user-defined functions via
#[protto(from_proto_fn = "...")]and#[protto(to_proto_fn = "...")]. - Ignored Fields: Exclude fields from conversion using
#[protto(ignore)]. - Advanced Error Handling: Support for custom error types and functions.
- Smart Optionality Detection: Automatic inference with manual override capabilities.
- Configurable Protobuf Module: Defaults to searching for types in a
protomodule, customizable per struct or globally.
Basic 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 Protto;
;
Attribute Reference
Struct-Level Attributes
#[protto(module = "path")]
Specifies the module path where protobuf types are located.
// looks for types in my_proto::*
#[protto(proto_name = "ProtoName")]
Maps the struct to a different protobuf type name.
// maps to proto::StateMessage
#[protto(error_type = ErrorType)]
Sets the error type for conversions that can fail.
#[protto(error_fn = "function_name")]
Specifies a function to handle conversion errors at the struct level.
Field-Level Attributes
#[protto(transparent)]
For newtype wrappers - directly converts the inner value.
] u64);
#[protto(proto_name = "proto_field_name")]
Maps the field to a different name in the protobuf.
pub id: u64, // maps to proto.user_id
#[protto(ignore)]
Excludes the field from proto conversion (uses Default::default).
pub runtime_data: ,
Custom Conversion Functions
#[protto(from_proto_fn = "function")]
Uses a custom function for proto → rust conversion.
pub created_at: ,
#[protto(to_proto_fn = "function")]
Uses a custom function for rust → proto conversion.
pub created_at: ,
Both can be combined for bidirectional custom conversion:
pub metadata: ,
Optionality Control
#[protto(proto_optional)]
Explicitly treats the proto field as optional.
pub field: String, // proto field is Option<String>, gets unwrapped
#[protto(proto_required)]
Explicitly treats the proto field as required.
pub field: , // proto field is String, gets wrapped
Error Handling
#[protto(expect)]
Uses .expect() with panic on missing optional fields.
pub required_field: String, // panics if proto field is None
#[protto(error_type = ErrorType)]
Field-level error type override.
#[protto(error_fn = "function")]
Custom error handling function for this field.
pub critical_field: String,
Default Values
#[protto(default)]
Uses Default::default() for missing/empty fields.
pub optional_field: String, // empty string if proto field is None
#[protto(default_fn = "function")]
Uses a custom function for default values.
pub username: String,
Advanced Examples
Complex conversions with custom functions
use HashMap;
Ignoring runtime fields
use AtomicU64;
Handling enums
enum Status {
STATUS_OK = 0;
STATUS_MOVED_PERMANENTLY = 1;
STATUS_FOUND = 2;
STATUS_NOT_FOUND = 3;
}
message StatusResponse {
Status status = 1;
string message = 2;
}
// Enum variant names are automatically mapped (prefix removal supported)
Error handling strategies
Optionality Handling
The macro automatically detects and handles optionality between proto and Rust types:
| Proto Type | Rust Type | Behavior |
|---|---|---|
string field = 1; |
String |
Direct assignment |
optional string field = 1; |
Option<String> |
Direct assignment |
optional string field = 1; |
String |
Use #[protto(expect)] or #[protto(default)] |
string field = 1; |
Option<String> |
Wrapped in Some() |
repeated string items = 1; |
Vec<String> |
Direct conversion |
repeated string items = 1; |
Option<Vec<String>> |
None for empty, Some(vec) otherwise |
Type Support
Primitives
All Rust primitives are supported: bool, u32, u64, i32, i64, f32, f64, String
Collections
Vec<T>↔repeated TOption<Vec<T>>↔repeated T(with empty handling)- Custom collections via
from_proto_fn/to_proto_fn
Optional Types
Option<T>↔optional T- Automatic unwrapping/wrapping with error handling
Custom Types
- Any type implementing
From/Intotraits - Newtype wrappers with
#[protto(transparent)] - Custom conversion functions
Enums
- Rust enums ↔ proto enums (with automatic prefix handling)
- Custom discriminant handling
Error Handling
The macro supports multiple error handling strategies:
- Panic: Use
#[protto(expect)]- panics with descriptive messages - Default Values: Use
#[protto(default)]- provides sensible defaults - Custom Errors: Use
#[protto(expect, error_type = T, error_fn = "f")]- custom error handling - Result Types: Generated
TryFromimplementations for fallible conversions
Limitations
- Assumes Protobuf-generated types live in a single module (configurable).
- Optional Protobuf message fields use
.expectand panic if missing (unless configured otherwise). - Complex nested generics may require custom conversion functions.
- Recursive types require careful handling of conversion cycles.
Best Practices
- Let the macro infer: Start without attributes and add them only when needed.
- Use transparent for newtypes:
#[protto(transparent)]for simple wrapper types. - Handle errors appropriately: Choose between panic, default, or custom error strategies.
- Test edge cases: Verify behavior with None/empty values and boundary conditions.
- Document custom functions: Make conversion logic clear for maintenance.