Prutal is a pure Go alternative to protocol buffers, it covers most of the functionality offered by Protocol Buffers.
Prutal aims to minimize code generation as much as possible while ensuring serialization and maintaining good performance.
Since Prutal is NOT yet ready for production use, we are not providing usage documentation at this time, nor do we guarantee backward compatibility of the interface.
| Features | Prutal | Protobuf |
|---|---|---|
| Supported Languages | Go | C++, Java, Python, Go, and more |
| Code Generation | ✅ | ✅ |
| Generated Code Size | 😄 compact | 😡 bulky |
| Serialization | ✅ | ✅ |
| Performance | ⭐️⭐️⭐️⭐️⭐️ | ⭐️⭐️⭐️ |
| Extensibility | 😄 Package | 😡 Plugin |
| Compatibility | ✅ (see Protobuf Compatibility) | ✅ |
| gRPC | ✅ | ✅ |
| Non-Pointer Field | ✅ (aka gogoproto.nullable) | ❌ |
- ✅ Works with code generated by the official Protocol Buffer Go (
protoc-gen-go), open API and hybrid API without theprotoopaquebuild tag - ❌ Opaque API (
default_api_level=API_OPAQUE, or the hybrid API built with-tags protoopaque): field presence lives in a bitmap the runtime does not maintain
- ✅ Marshal / Unmarshal — binary wire format
- ✅ MarshalAppend — append-friendly encoding
- ✅ Size — compute wire size without marshaling (zero allocation)
- ✅ Reset
- ❌ Clone / Merge / Equal
- ❌ CheckInitialized — validate required field presence
- ❌ MarshalOptions / UnmarshalOptions (deterministic output, discard unknown, recursion limits, etc.)
- ✅ Scalar types: all 15 protobuf scalar types
- ✅ Messages and nested messages
- ✅ Repeated fields
- ✅ Enums (including
allow_alias) - ✅ Optional / field presence
- The presence of a
bytesfield is read from the struct tag asprotoc-gen-gowrites it: a field of a proto3 file carriesproto3, a proto3 optional fieldoneofon top of it, and any other singular field has presence. prutalgenwrites the same markers, plus its ownimplicitmarker for an edition 2023 field withfeatures.field_presence = IMPLICIT, whichprotoc-gen-goleaves unmarked.- Code generated by earlier
prutalgenversions carries no markers and is read as having presence: abytesfield without presence, proto3 or editions implicit, holding an empty non-nil slice is serialized as an empty field instead of being skipped. Decoders read it as the default value; regenerate to get the exact output. protoc-gen-gooutput for an edition 2023 implicitbytesfield has the same divergence, as the runtime cannot tell it from a field with presence.
- The presence of a
- ✅ Map fields
- ✅ Oneof fields
- ✅ Required fields (proto2)
- ✅ Packed / unpacked encoding: written as declared, both forms are accepted on decode
- PACKED / EXPANDED (repeated field encoding, edition 2023)
- ✅ Unknown field preservation
- ✅ Reserved fields and names
- ❌ Default scalar values (proto2, edition 2023)
- ❌ Groups (proto2, deprecated)
- ❌ UTF-8 validation
- ❌ Message sets (legacy format)
- ✅ Binary wire format
⚠️ JSON: struct tag only, noprotojsonequivalent- ❌ Text format (
prototext) - ❌ Delimited format (length-prefixed message streams)
- ❌ Descriptor-based reflection (
protoreflect) - ❌ Type registries (
protoregistry) - ❌ Dynamic messages (
dynamicpb)
⚠️ Extensions (proto2): parsed but limited codegen- ❌ Has / Get / Set / Clear / Range extensions
⚠️ Extensions set throughprotobuf-goare dropped by Marshal; on Unmarshal they are kept as unknown fields and written back as such
- ✅ Proto2, proto3, edition 2023
- ✅ Imports (regular, public, weak)
- ✅ gRPC / Services (client/server stubs, streaming)
⚠️ Go only⚠️ Well-known types: reusesgoogle.golang.org/protobuf/types/known,Anyis limited⚠️ Custom options: subset of gogoproto options- ❌ Edition 2024
- ❌ Opaque API / lazy decoding
- ❌ Deprecation markers
Use pkg/grpccodec and pass grpccodec.PrutalCodec to the gRPC v1 codec APIs on both server and client.
See pkg/grpccodec/README.md for usage details and tests/cases/grpc for a complete example.
Contributor guide: Contributing.
Prutal is licensed under the terms of the Apache license 2.0. See LICENSE for more information.
Dependencies used by prutal are listed under licenses.