跳转至

Proto3 语法完全手册

Protocol Buffers(简称 Protobuf)是 Google 开源的一种语言无关、平台无关且高度可扩展的结构化数据序列化机制。当前在 gRPC 体系中,Proto3 是事实上的标准语法版本。


1. 基础结构与标量类型

一个标准的 .proto 文件结构如下:

syntax = "proto3"; // 必须在第一行非注释非空行声明,否则默认为 proto2

package commerce.order.v1; // 命名空间,防止命名冲突

// 针对多语言生成的代码选项
option go_package = "example.com/commerce/order/v1;orderv1";
option java_multiple_files = true;
option java_package = "com.commerce.order.v1";

message Order {
  string order_id = 1;      // 字符串类型
  int64 user_id = 2;        // 64 位整型
  double total_amount = 3;  // 双精度浮点
  bool is_paid = 4;         // 布尔类型
  bytes payload = 5;        // 二进制原始字节流
}

标量类型(Scalar Value Types)映射表

.proto 类型 描述与编码考量 C++ 映射 Go 映射 Python 映射
double 64 位双精度浮点数(固定 8 字节) double float64 float
float 32 位单精度浮点数(固定 4 字节) float float32 float
int32 / int64 变长整数(Varint 编码)。注意:负数需耗费 10 字节! int32 / int64 int32 / int64 int
uint32 / uint64 变长无符号整数(Varint 编码) uint32 / uint64 uint32 / uint64 int
sint32 / sint64 采用 ZigZag 编码。对包含大量负数的整型极具空间优势 int32 / int64 int32 / int64 int
fixed32 / fixed64 固定 4/8 字节。当数值通常大于 $2^{28}$ 时,比 Varint 更高效 uint32 / uint64 uint32 / uint64 int
sfixed32 / sfixed64 固定长度有符号整型 int32 / int64 int32 / int64 int
bool 布尔型(1 字节) bool bool bool
string UTF-8 编码或 7 位 ASCII 文本 std::string string str
bytes 任意长度二进制流 std::string []byte bytes

2. 默认值规则(Default Values)

在 Proto3 中,当反序列化或未显式赋值时,字段将自动赋予语言环境对应的默认零值:

  • string:空字符串 ""
  • bytes:空字节序列 b""[]byte{}
  • boolfalse
  • 数值类型(int, float 等)0
  • enum 枚举:首个枚举元素(其数值必须为 0
  • message 嵌套对象:语言相关的空对象指针(如 Go 中的 nil,Java 中的 null
  • repeated 集合:空列表

[!WARNING] 零值不占用网络传输带宽!在 Proto3 中,如果某个标量字段的值等于其默认零值,该字段不会被序列化到二进制数据流中。接收端若未读到该字段,会自动恢复为默认值。这虽然节省了带宽,但也意味着在原生 Proto3 中无法直接区分“字段未赋值”与“字段被显式赋值为零”。若需区分,可使用 optional 关键字或包装类型。


3. 枚举类型(Enumerations)

enum PaymentStatus {
  // 第一个枚举常量的值必须为 0,作为默认零值
  PAYMENT_STATUS_UNSPECIFIED = 0;
  PAYMENT_STATUS_PENDING = 1;
  PAYMENT_STATUS_COMPLETED = 2;
  PAYMENT_STATUS_FAILED = 3;
}

[!IMPORTANT] 枚举命名规范:推荐枚举以 _UNSPECIFIED = 0 作为首项,并添加类型前缀,防止多枚举全局符号冲突。


4. 复合类型:Repeated、Map 与 Nested

① 切片/数组 (repeated)

表示零个或多个元素的有序列表(默认启用 Packed 编码以节省空间):

message Cart {
  repeated string item_ids = 1;
  repeated Item items = 2;
}

② 键值字典 (map)

message UserAttributes {
  // 语法:map<key_type, value_type> map_field = N;
  // key_type 不能是 float、double 或 bytes
  map<string, string> metadata = 1;
}

③ 嵌套消息定义

message OrderResponse {
  message OrderDetail {
    string id = 1;
    double price = 2;
  }

  OrderDetail detail = 1; // 引用内部嵌套消息
}

5. 高级类型特性

① 互斥字段:oneof

若消息中包含若干字段,但在业务逻辑中同一时刻至多只有一个字段会被赋值,可以使用 oneof。它能大幅节省内存开销:

message PaymentMethod {
  string account_id = 1;

  oneof channel {
    CreditCard credit_card = 2;
    WeChatPay wechat_pay = 3;
    AliPay alipay = 4;
  }
}

② 动态多态类型:google.protobuf.Any

允许在未提前编译引用该类型的前提下嵌入任意 Protobuf 消息。它底层包含实际序列化字节流与类型标识 URL(type_url):

import "google/protobuf/any.proto";

message ErrorResponse {
  int32 code = 1;
  string message = 2;
  repeated google.protobuf.Any details = 3; // 携带任意类型的详细调试对象
}

③ 官方常见类型(Well-Known Types)

Google 官方提供了大量经过严格设计的标准库,建议在日常开发中优先复用: - google/protobuf/timestamp.proto:精确到纳秒的 UTC 时间戳。 - google/protobuf/duration.proto:时间间隔长度。 - google/protobuf/empty.proto:无入参或无返回值的空消息体。 - google/protobuf/field_mask.proto:用于 PATCH 部分更新的字段掩码。 - google/protobuf/wrappers.proto:将基本类型(Int32Value, StringValue)包装为对象,以支持 null 语义。