Protobuf style#
The Pigweed protobuf style guide is closely based on Google’s public protobuf style guide. The Google Protobuf Style Guide applies to Pigweed except as described in this document.
The Pigweed protobuf style guide only applies to Pigweed itself. It does not apply to projects that use Pigweed or to the third-party code included with Pigweed.
Editions#
Pigweed only allows proto3
(i.e. syntax = "proto3";
) at this time.
proto2
is not supported, and the edition
versions (e.g.
edition = "2023";
) will be considered when sufficient motivation arises.
Naming#
Protobuf library structure#
All protobuf files should live in a proto
subdirectory within their
respective modules. This produces an import
path that matches the pattern of
[module name]/proto/[proto file].proto
. If a module exposes multiple proto
libraries that need to be grouped separately from a build/distribution
perspective, additional directories may be introduced that follow the pattern
[module name]/[group name]_proto/[proto file].proto
.
Examples:
pw_log/proto
pw_unit_test/proto
pw_snapshot/proto
pw_snapshot/metadata_proto
Rationale: This overlays the include paths of native libraries, but introduces
proto
to the enclosing directory hierarchy to make it clearer that generated
proto libraries are being introduced.
Protobuf package names#
Protobuf library packages should append a proto
suffix to package
namespaces. If a module exposes multiple proto
libraries that need to be grouped separately from a build/distribution
perspective, a *_proto
suffix may be used instead.
Example:
// This lives inside of pw_file.
package pw.file.proto;
service FileSystem {
// ...
}
Rationale: This prevents collisions between generated proto types and language-native types.
RPC service names#
Services should strive for simple, intuitive, globally unique names, and should
not be suffixed with *Service
.
Example:
service PwEcho {
rpc Echo(EchoRequest) returns (EchoResponse) {}
}
Rationale: Pigweed’s C++ RPC codegen namespaces services differently than
regular protos (for example, pw::file::pw_rpc::raw::FileSystem::Service
),
which makes it sufficiently clear when a name refers to a service.
Typing#
Using optional
#
When presence or absence of a field’s value has a semantic meaning, the field
should be marked as optional
to signal the distinction. Otherwise,
optional
should be omitted. This causes the field to still be optional, but
indicates that the default value of zero is semantically equivalent to an
explicit value of zero.
Example:
message MyRequest {
// The maximum number of `foo` entries to return in the response message.
// If this is not set, the response may contain as many `foo` entries
// as needed. If `max_foo` is zero, no `foo` entries should be included
// in the response.
optional uint32 max_foo = 1;
}
Rationale: optional
is very useful for clarifying cases where an implied
default value seems ambiguous, and it also signals codegen for has_max_foo
getters in various languages.
Using required
#
required
fields are strictly forbidden within Pigweed.
Enums#
Default values#
All enums must have a safe default zero value. Usually this is UNKNOWN
or
NONE
, but may be any other semantically similar default.
Rationale: Enums are default-initialized to zero, which means that a zero value that conveys anything beyond “unset” may be misleading.
package pw.chrono.proto;
message EpochType {
enum Enum {
UNKNOWN = 0;
TIME_SINCE_BOOT = 1;
UTC_WALL_CLOCK = 2;
GPS_WALL_CLOCK = 3;
TAI_WALL_CLOCK = 4;
};
}
Namespacing#
Prefer to place enum
definitions within a message
to namespace the
generated names for the values.
Rationale: Enum value names can easily collide if you don’t prefix them (i.e.
UNKNOWN
from one enum will collide with UNKNOWN
from another enum).
Namespacing them within a message prevents these collisions.
package pw.chrono.proto;
message EpochType {
enum Enum {
UNKNOWN = 0;
TIME_SINCE_BOOT = 1;
UTC_WALL_CLOCK = 2;
GPS_WALL_CLOCK = 3;
TAI_WALL_CLOCK = 4;
};
}