# 一句話總結
protobuf 對欄位「多/少」天生寬容(多的忽略、少的給零值),不會自動噴錯;真正會炸的是「型別不符」或「欄位編號被亂改」。要嚴格驗證得自己在 server 邏輯做。
# TL;DR — 三種情況速查
| 情況 | 結果 | 為什麼 |
|---|---|---|
| 少欄位(client 沒帶某欄位) | ✅ 不報錯,該欄位拿型別零值 | proto3 沒有 required,每個欄位天生 optional |
| 多欄位 / client 比 server 新(帶了 server 不認得的欄位) | ✅ 不報錯,server 忽略並丟棄(unknown fields) | server 解碼時遇到不認得的 field number 就跳過 |
| 同名但型別不符(例如該是 string 卻塞 int) | ❌ 解碼失敗 / 噴錯 | wire type 對不上,無法解析 |
關鍵:protobuf 比對的是欄位編號(field number),不是欄位名稱。所以 .proto 裡那些 = 1、= 12 的編號才是真正的契約。
# 為什麼「少欄位」不會錯 — 零值陷阱
proto3 沒有 required、也預設不區分「沒帶」與「帶了零值」。client 少帶一個欄位,server 收到的就是零值:
| 型別 | 零值 |
|---|---|
string |
"" |
int32 |
0 |
bool |
false |
message |
nil(指標) |
⚠️ 這正是實務上最容易踩的雷:server 收到 score = 0 時,分不清是「client 真的傳 0」還是「client 根本沒帶」。需要區分時要用 optional(proto3 的 explicit presence,會生成 *float32 指標,nil = 沒帶)。
官方文件說明:proto3 的 scalar 預設沒有 presence,要區分「沒帶」和「零值」必須用
optional關鍵字(Field Presence)。
# 為什麼「多欄位」不會錯 — 單邊升級的底氣
server 解碼時遇到不認得的 field number,會把它存進「unknown fields」然後忽略。
官方 Encoding guide 指出,protobuf 是「self-describing」的 wire format:tag 裡帶了 field number 與 wire type,parser 看到不認得的 field number 會跳過對應長度的 payload,繼續往下 parse(Encoding)。
這就是為什麼可以「單邊升級」:
- 新 client + 舊 server:新 client 送舊 server 不認得的欄位,舊 server 忽略。
- 舊 client + 新 server:新 server 收到舊 client 沒送的欄位,給零值;新 server 想送新欄位給舊 client 也沒問題,舊 client 直接丟進 unknown fields。
整個前後向相容性(forward / backward compatibility)就是建立在「多/少欄位都容忍」之上。
# 什麼情況真的會噴錯
- 型別 / wire-type 不符 — 解碼失敗,回 gRPC error(通常
InvalidArgument或 codec 層Internal)。 - 欄位編號被重用 / 改了型別 — 最危險,新舊兩邊把同一個編號解成不同東西,資料錯亂。所以:
- proto 改欄位只能加新編號,不能改舊編號的型別
- 移除欄位要
reserved,避免未來不小心重用
- method 根本不存在(不是欄位問題)—
Unimplemented - application 層自己驗 — protobuf 不擋,但你 server code 可以自己檢查「這欄位空的話我回 error」(這是你寫的邏輯在擋,不是 protobuf 在擋)
# 演化紀律:只加新編號、不動舊的
因為上面的規則,實務上 proto 演化要遵守:
- ✅ 新增欄位:給新的 field number(永遠往後加,不要回頭插)
- ✅ 移除欄位:用
reserved標記那些編號,別人未來也不能用 - ❌ 改欄位名稱:技術上 wire format 不影響(名稱根本沒上線),但會讓 .proto 維護混亂
- ❌ 改欄位編號:直接壞,新舊 wire format 完全對不上
- ❌ 改欄位型別(用同一個編號):wire type 對不上,直接解碼失敗
所以我們改 proto 時一直堅持「只加新編號、不動舊的」—— 就是不踩「欄位編號重用」那顆雷。
# 參考資料
- Language Guide (proto 3) — 完整 proto3 規範
- Encoding — wire format、field number、unknown fields 的底層說明
- Field Presence — proto3 presence 語意、
optional的用法
