Go 常見問題

關於在 Go 中實作 Protocol Buffer 的常見問題列表,以及每個問題的解答。

版本

github.com/golang/protobufgoogle.golang.org/protobuf 之間有什麼差異?

github.com/golang/protobuf 模組是原始的 Go Protocol Buffer API。

google.golang.org/protobuf 模組是此 API 的更新版本,旨在簡化、易用和安全。更新版 API 的旗艦功能是支援反射,以及將使用者介面 API 與底層實作分離。

我們建議您在新程式碼中使用 google.golang.org/protobuf

github.com/golang/protobufv1.4.0 及更高版本包裝了新的實作,並允許程式逐步採用新的 API。例如,github.com/golang/protobuf/ptypes 中定義的 Well-Known Types 只是較新模組中定義的別名。因此,google.golang.org/protobuf/types/known/emptypbgithub.com/golang/protobuf/ptypes/empty 可以互換使用。

proto1proto2proto3 是什麼?

這些是 Protocol Buffer「語言」的修訂版本。它與 protobuf 的 Go「實作」不同。

  • proto3 是目前版本的語言。這是最常用的語言版本。我們鼓勵新程式碼使用 proto3。

  • proto2 是較舊版本的語言。儘管 proto3 已取代 proto2,但 proto2 仍完全受到支援。

  • proto1 是已過時版本的語言。它從未以開放原始碼形式發布。

有幾種不同的 Message 類型。我應該使用哪一種?

常見問題

go install”:working directory is not part of a module

在 Go 1.15 及更低版本中,您已設定環境變數 GO111MODULE=on,並且在模組目錄外部執行 go install 命令。設定 GO111MODULE=auto,或取消設定環境變數。

在 Go 1.16 及更高版本中,可以透過指定明確版本在模組外部調用 go installgo install google.golang.org/protobuf/cmd/protoc-gen-go@latest

常數 -1 溢位 protoimpl.EnforceVersion

您正在使用產生的 .pb.go 檔案,該檔案需要較新版本的 "google.golang.org/protobuf" 模組。

使用以下命令更新到較新版本:

go get -u google.golang.org/protobuf/proto

未定義:"github.com/golang/protobuf/proto".ProtoPackageIsVersion4

您正在使用產生的 .pb.go 檔案,該檔案需要較新版本的 "github.com/golang/protobuf" 模組。

使用以下命令更新到較新版本:

go get -u github.com/golang/protobuf/proto

什麼是 Protocol Buffer 命名空間衝突?

連結到 Go 二進位檔的所有 Protocol Buffer 宣告都會插入到全域登錄檔中。

每個 protobuf 宣告(例如,列舉、列舉值或訊息)都有一個絕對名稱,它是 套件名稱.proto 原始檔中宣告的相對名稱的串連(例如,my.proto.package.MyMessage.NestedMessage)。protobuf 語言假設所有宣告都是通用唯一的。

如果連結到 Go 二進位檔的兩個 protobuf 宣告具有相同的名稱,則會導致命名空間衝突,並且登錄檔無法透過名稱正確解析該宣告。根據使用的 Go protobuf 版本,這會在初始化時發生 panic,或靜默地捨棄衝突並導致稍後在執行階段可能發生的錯誤。

我該如何修正 Protocol Buffer 命名空間衝突?

修正命名空間衝突的最佳方法取決於發生衝突的原因。

命名空間衝突發生的常見方式

  • 供應商提供的 .proto 檔案。 當單個 .proto 檔案產生到兩個或多個 Go 套件中,並連結到同一個 Go 二進位檔時,它會在產生的 Go 套件中的每個 protobuf 宣告上發生衝突。當 .proto 檔案是供應商提供的,並從中產生 Go 套件,或者產生的 Go 套件本身是供應商提供的時,通常會發生這種情況。使用者應避免供應商提供的檔案,而是依賴該 .proto 檔案的集中式 Go 套件。

    • 如果 .proto 檔案由外部方擁有,並且缺少 go_package 選項,則您應與該 .proto 檔案的所有者協調,以指定大多數使用者都可以依賴的集中式 Go 套件。
  • 遺失或通用 Proto 套件名稱。 如果 .proto 檔案未指定套件名稱或使用過於通用的套件名稱(例如,“my_service”),則該檔案中的宣告很可能與宇宙中其他地方的宣告衝突。我們建議每個 .proto 檔案都有一個經過刻意選擇的套件名稱,使其具有通用唯一性(例如,以公司名稱為前綴)。

google.golang.org/protobuf 模組的 v1.26.0 開始,當 Go 程式啟動時,如果連結到其中的多個 protobuf 名稱發生衝突,則會報告硬性錯誤。雖然最好修正衝突的來源,但可以立即透過以下兩種方式之一解決致命錯誤:

  1. 在編譯時。 可以在編譯時使用連結器初始化的變數指定處理衝突的預設行為:go build -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn"

  2. 在程式執行時。 可以在執行特定 Go 二進位檔時使用環境變數設定處理衝突的行為:GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn ./main

為什麼 reflect.DeepEqual 在 Protocol Buffer 訊息中行為異常?

產生的 Protocol Buffer 訊息類型包含內部狀態,即使在等效訊息之間也可能有所不同。

此外,reflect.DeepEqual 函數不知道 Protocol Buffer 訊息的語意,並且可能會報告不存在的差異。例如,包含 nil map 的 map 欄位和包含零長度、非 nil map 的欄位在語意上是等效的,但 reflect.DeepEqual 會將它們報告為不相等。

使用 proto.Equal 函數比較訊息值。

在測試中,您也可以將 "github.com/google/go-cmp/cmp" 套件與 protocmp.Transform() 選項搭配使用。cmp 套件可以比較任意資料結構,而 cmp.Diff 會產生人類可讀的數值差異報告。

if diff := cmp.Diff(a, b, protocmp.Transform()); diff != "" {
  t.Errorf("unexpected difference:\n%v", diff)
}

Hyrum 定律

什麼是 Hyrum 定律,為什麼它會出現在這個常見問題中?

Hyrum 定律指出:

當 API 的使用者數量足夠多時,您在合約中承諾什麼並不重要:您系統的所有可觀察行為都將被某些人依賴。

最新版本 Go Protocol Buffer API 的設計目標是在可能的情況下避免提供我們無法承諾在未來保持穩定的可觀察行為。我們的理念是,在我們不做出任何承諾的領域中,刻意的不穩定性勝過給人穩定的錯覺,而這種穩定性僅會在未來在專案可能長期依賴該錯誤假設之後發生變化。

為什麼錯誤訊息的文字一直變更?

依賴錯誤確切文字的測試很脆弱,並且在文字變更時經常會中斷。為了阻止在測試中不安全地使用錯誤文字,此模組產生的錯誤文字是刻意不穩定的。

如果您需要識別錯誤是否由 protobuf 模組產生,我們保證所有錯誤都將根據 errors.Is 符合 proto.Error

為什麼 protojson 的輸出一直變更?

我們不保證 Go 實作的 Protocol Buffer JSON 格式 的長期穩定性。該規格僅指定什麼是有效的 JSON,但未提供關於 Marshaler 應「完全」格式化給定訊息的「標準」格式的規格。為了避免給人輸出穩定的錯覺,我們刻意引入微小的差異,以便逐位元組比較很可能會失敗。

為了獲得一定程度的輸出穩定性,我們建議透過 JSON 格式器執行輸出。

為什麼 prototext 的輸出一直變更?

我們不保證 Go 實作的文字格式的長期穩定性。protobuf 文字格式沒有標準規格,我們希望保留未來改進 prototext 套件輸出的能力。由於我們不保證套件輸出的穩定性,因此我們刻意引入不穩定性以阻止使用者依賴它。

為了獲得一定程度的穩定性,我們建議透過 txtpbfmt 程式傳遞 prototext 的輸出。可以使用 parser.Format 在 Go 中直接調用格式器。

其他

我該如何將 Protocol Buffer 訊息當作雜湊鍵使用?

您需要標準序列化,其中 Protocol Buffer 訊息的 Marshaled 輸出保證在一段時間內保持穩定。遺憾的是,目前沒有標準序列化的規格。您需要編寫自己的規格或找到一種方法來避免需要規格。

我可以為 Go Protocol Buffer 實作新增功能嗎?

也許可以。我們總是樂於接受建議,但我們對於新增事物非常謹慎。

Protocol Buffer 的 Go 實作力求與其他語言實作保持一致。因此,我們傾向於避開過於專門針對 Go 的功能。Go 專用功能會阻礙 Protocol Buffer 作為語言中性資料交換格式的目標。

除非您的想法專門針對 Go 實作,否則您應該加入 protobuf 討論群組並在那裡提出建議。

如果您對 Go 實作有想法,請在我們的問題追蹤器上提交問題:https://github.com/golang/protobuf/issues

我可以為 MarshalUnmarshal 新增選項以自訂它嗎?

只有當該選項存在於其他實作(例如,C++、Java)中時才行。Protocol Buffer 的編碼(二進位、JSON 和文字)必須在各種實作中保持一致,以便以一種語言編寫的程式能夠讀取由另一種語言編寫的訊息。

除非至少在另一種受支援的實作中存在等效選項,否則我們不會在 Go 實作中新增任何會影響 Marshal 函數輸出的資料或 Unmarshal 函數讀取的資料的選項。

我可以自訂 protoc-gen-go 產生的程式碼嗎?

一般而言,不行。Protocol Buffer 旨在成為一種與語言無關的資料交換格式,而特定於實作的自訂與該意圖背道而馳。