Go FAQ
Versionen
Was ist der Unterschied zwischen github.com/golang/protobuf und google.golang.org/protobuf?
Das Modul github.com/golang/protobuf ist die ursprüngliche Go-Protokollpuffer-API.
Das Modul google.golang.org/protobuf ist eine aktualisierte Version dieser API, die auf Einfachheit, Benutzerfreundlichkeit und Sicherheit ausgelegt ist. Die Hauptmerkmale der aktualisierten API sind die Unterstützung für Reflexion und eine Trennung der benutzersichtigen API von der zugrunde liegenden Implementierung.
Wir empfehlen die Verwendung von google.golang.org/protobuf für neuen Code.
Version v1.4.0 und höher von github.com/golang/protobuf umschließen die neue Implementierung und erlauben Programmen, die neue API schrittweise zu übernehmen. Zum Beispiel sind die bekannten Typen, die in github.com/golang/protobuf/ptypes definiert sind, einfach Aliase für diejenigen, die im neueren Modul definiert sind. Somit können google.golang.org/protobuf/types/known/emptypb und github.com/golang/protobuf/ptypes/empty austauschbar verwendet werden.
Was sind proto1, proto2, proto3 und Editionen?
Dies sind Überarbeitungen der Protokollpuffer-Sprache. Sie unterscheiden sich von der Go-Implementierung von Protokollpuffern.
Editionen sind der neueste und empfohlene Weg, Protokollpuffer zu schreiben. Neue Features werden als Teil neuer Editionen veröffentlicht. Weitere Informationen finden Sie unter Protokollpuffer-Editionen.
proto3ist eine ältere Version der Sprache. Wir empfehlen, für neuen Code Editionen zu verwenden.proto2ist eine ältere Version der Sprache. Obwohl es von proto3 und Editionen abgelöst wurde, wird proto2 weiterhin vollständig unterstützt.proto1ist eine veraltete Version der Sprache. Sie wurde nie als Open Source veröffentlicht.
Es gibt verschiedene Message-Typen. Welchen sollte ich verwenden?
"google.golang.org/protobuf/proto".Messageist ein Schnittstellentyp, der von allen Nachrichten implementiert wird, die vom aktuellen Protokollpuffer-Compiler generiert werden. Funktionen, die mit beliebigen Nachrichten arbeiten, wieproto.Marshaloderproto.Clone, akzeptieren oder geben diesen Typ zurück."google.golang.org/protobuf/reflect/protoreflect".Messageist ein Schnittstellentyp, der eine Reflexionsansicht einer Nachricht beschreibt.Rufen Sie die Methode
ProtoReflectauf einerproto.Messageauf, um eineprotoreflect.Messagezu erhalten."google.golang.org/protobuf/reflect/protoreflect".ProtoMessageist ein Alias für"google.golang.org/protobuf/proto".Message. Die beiden Typen sind austauschbar."github.com/golang/protobuf/proto".Messageist eine Schnittstelle, die von der älteren Go-Protokollpuffer-API definiert wird. Alle generierten Nachrichtentypen implementieren diese Schnittstelle, aber die Schnittstelle beschreibt nicht das erwartete Verhalten dieser Nachrichten. Neuer Code sollte die Verwendung dieses Typs vermeiden.
Häufige Probleme
„go install“: Verzeichnis ist nicht Teil eines Moduls
Bei Go 1.15 und früher haben Sie die Umgebungsvariable GO111MODULE=on gesetzt und den Befehl go install außerhalb eines Modulverzeichnisses ausgeführt. Setzen Sie GO111MODULE=auto oder löschen Sie die Umgebungsvariable.
Ab Go 1.16 kann go install außerhalb eines Moduls aufgerufen werden, indem eine explizite Version angegeben wird: go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
konstante -1 überläuft protoimpl.EnforceVersion
Sie verwenden eine generierte .pb.go-Datei, die eine neuere Version des Moduls "google.golang.org/protobuf" erfordert.
Aktualisieren Sie mit
go get -u google.golang.org/protobuf/proto
undefiniert: „github.com/golang/protobuf/proto“.ProtoPackageIsVersion4
Sie verwenden eine generierte .pb.go-Datei, die eine neuere Version des Moduls "github.com/golang/protobuf" erfordert.
Aktualisieren Sie mit
go get -u github.com/golang/protobuf/proto
Was ist ein Protokollpuffer-Namensraumkonflikt?
Alle Protokollpuffer-Deklarationen, die in eine Go-Binärdatei gelinkt werden, werden in eine globale Registrierung eingefügt.
Jede Protokollpuffer-Deklaration (z. B. Enums, Enum-Werte oder Nachrichten) hat einen absoluten Namen, der sich aus dem Paketnamen und dem relativen Namen der Deklaration in der .proto-Quelldatei zusammensetzt (z. B. my.proto.package.MyMessage.NestedMessage). Die Protokollpuffer-Sprache geht davon aus, dass alle Deklarationen universell eindeutig sind.
Wenn zwei in eine Go-Binärdatei gelinkte Protokollpuffer-Deklarationen denselben Namen haben, führt dies zu einem Namensraumkonflikt, und die Registrierung kann diese Deklaration nicht ordnungsgemäß nach Namen auflösen. Je nach verwendeter Version von Go-Protokollpuffern führt dies entweder zu einem Panic zur Initialisierungszeit oder zu einem stillen Ignorieren des Konflikts, was später zu einem Laufzeitfehler führen kann.
Wie behebe ich einen Protokollpuffer-Namensraumkonflikt?
Die beste Methode zur Behebung eines Namensraumkonflikts hängt von der Ursache des Konflikts ab.
Häufige Ursachen für Namensraumkonflikte
Vendored .proto-Dateien. Wenn eine einzelne
.proto-Datei in zwei oder mehr Go-Pakete generiert und in dieselbe Go-Binärdatei gelinkt wird, kommt es zu einem Konflikt bei jeder Protokollpuffer-Deklaration in den generierten Go-Paketen. Dies geschieht typischerweise, wenn eine.proto-Datei als Vendor-Datei eingebunden und daraus ein Go-Paket generiert wird, oder wenn das generierte Go-Paket selbst als Vendor-Datei eingebunden wird. Benutzer sollten das Vendoring vermeiden und stattdessen auf ein zentralisiertes Go-Paket für diese.proto-Datei setzen.- Wenn eine
.proto-Datei im Besitz eines externen Dienstleisters ist und die Optiongo_packagefehlt, sollten Sie sich mit dem Eigentümer dieser.proto-Datei abstimmen, um ein zentralisiertes Go-Paket anzugeben, auf das eine Vielzahl von Benutzern alle zugreifen können.
- Wenn eine
Fehlende oder generische Proto-Paketnamen. Wenn eine
.proto-Datei keinen Paketnamen angibt oder einen zu generischen Paketnamen verwendet (z. B. „my_service“), besteht eine hohe Wahrscheinlichkeit, dass Deklarationen in dieser Datei mit anderen Deklarationen irgendwo anders im Universum kollidieren. Wir empfehlen, dass jede.proto-Datei einen Paketnamen hat, der bewusst so gewählt wird, dass er universell eindeutig ist (z. B. mit dem Namen eines Unternehmens als Präfix).
Warnung
Das nachträgliche Ändern des Paketnamens einer.proto-Datei ist für als Erweiterungsfelder verwendete Typen, in google.protobuf.Any gespeicherte Daten oder für gRPC-Dienstdefinitionen nicht abwärtskompatibel.Ab Version v1.26.0 des Moduls google.golang.org/protobuf wird beim Start eines Go-Programms, das mehrere kollidierende Protokollpuffer-Namen enthält, ein schwerwiegender Fehler gemeldet. Obwohl es vorzuziehen ist, die Ursache des Konflikts zu beheben, kann der fatale Fehler sofort auf eine von zwei Arten umgangen werden:
Zur Kompilierzeit. Das Standardverhalten für die Behandlung von Konflikten kann zur Kompilierzeit mit einer Linker-initialisierten Variablen festgelegt werden:
go build -ldflags "-X google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn"Zur Programmausführung. Das Verhalten für die Behandlung von Konflikten bei der Ausführung einer bestimmten Go-Binärdatei kann mit einer Umgebungsvariablen festgelegt werden:
GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn ./main
Wie verwende ich Protokollpuffer-Editionen?
Um eine Protokollpuffer-Edition zu verwenden, müssen Sie die Edition in Ihrer .proto-Datei angeben. Um beispielsweise die Edition 2023 zu verwenden, fügen Sie Folgendes am Anfang Ihrer .proto-Datei hinzu:
edition = "2023";
Der Protokollpuffer-Compiler generiert dann Go-Code, der mit der angegebenen Edition kompatibel ist. Mit Editionen können Sie auch bestimmte Features für Ihre .proto-Datei aktivieren oder deaktivieren. Weitere Informationen finden Sie unter Protokollpuffer-Editionen.
Wie steuere ich das Verhalten meines generierten Go-Codes?
Mit Editionen können Sie das Verhalten des generierten Go-Codes steuern, indem Sie bestimmte Features in Ihrer .proto-Datei aktivieren oder deaktivieren. Um beispielsweise das API-Verhalten für Ihre Implementierung festzulegen, würden Sie Folgendes zu Ihrer .proto-Datei hinzufügen:
edition = "2023";
option features.(pb.go).api_level = API_OPAQUE;
Wenn api_level auf API_OPAQUE gesetzt ist, verbirgt der von der Protokollpuffer-Compiler generierte Go-Code die Strukturfelder, sodass sie nicht mehr direkt zugänglich sind. Stattdessen werden neue Zugriffsmethoden zum Abrufen, Festlegen oder Löschen eines Felds erstellt.
Eine vollständige Liste der verfügbaren Features und deren Beschreibungen finden Sie unter Features für Editionen.
Warum verhält sich reflect.DeepEqual unerwartet mit Protokollpuffer-Nachrichten?
Generierte Protokollpuffer-Nachrichtentypen enthalten interne Zustände, die sich selbst zwischen äquivalenten Nachrichten unterscheiden können.
Darüber hinaus ist die Funktion reflect.DeepEqual nicht mit den Semantiken von Protokollpuffer-Nachrichten vertraut und kann Unterschiede melden, wo keine vorhanden sind. Beispielsweise sind ein Kartenfeld, das eine nil-Karte enthält, und eines, das eine Karte der Länge Null enthält, semantisch äquivalent, werden aber von reflect.DeepEqual als ungleich gemeldet.
Verwenden Sie die Funktion proto.Equal, um Nachrichtenwerte zu vergleichen.
In Tests können Sie auch das Paket "github.com/google/go-cmp/cmp" mit der Option protocmp.Transform() verwenden. Das cmp-Paket kann beliebige Datenstrukturen vergleichen, und cmp.Diff erzeugt lesbare Berichte über die Unterschiede zwischen Werten.
if diff := cmp.Diff(a, b, protocmp.Transform()); diff != "" {
t.Errorf("unexpected difference:\n%v", diff)
}
Hyrum’s Law
Was ist Hyrum’s Law und warum ist es in diesem FAQ enthalten?
Hyrum’s Law besagt
Mit einer ausreichenden Anzahl von Benutzern einer API spielt es keine Rolle, was Sie im Vertrag versprechen: Alle beobachtbaren Verhaltensweisen Ihres Systems werden von jemandem in Anspruch genommen.
Ein Designziel der neuesten Version der Go-Protokollpuffer-API ist es, wo möglich, beobachtbare Verhaltensweisen zu vermeiden, deren Stabilität wir in Zukunft nicht versprechen können. Es ist unsere Philosophie, dass bewusste Instabilität in Bereichen, für die wir keine Versprechen abgeben, besser ist, als die Illusion von Stabilität zu erwecken, nur damit sich diese in Zukunft ändert, nachdem ein Projekt potenziell lange von dieser falschen Annahme abhängig war.
Warum ändert sich der Text von Fehlermeldungen ständig?
Tests, die vom exakten Text von Fehlermeldungen abhängen, sind fehleranfällig und brechen oft, wenn sich dieser Text ändert. Um die unsichere Verwendung von Fehlermeldungstexten in Tests zu entmutigen, ist der Text von Fehlermeldungen, die von diesem Modul erzeugt werden, absichtlich instabil.
Wenn Sie feststellen müssen, ob ein Fehler vom protobuf-Modul erzeugt wurde, garantieren wir, dass alle Fehler proto.Error gemäß errors.Is entsprechen.
Warum ändert sich die Ausgabe von protojson ständig?
Wir geben keine Garantien für die Langzeitstabilität der Go-Implementierung des JSON-Formats für Protokollpuffer. Die Spezifikation legt nur fest, was gültiges JSON ist, aber keine Spezifikation für ein kanonisches Format liefert, wie ein Marshaler eine gegebene Nachricht exakt formatieren sollte. Um die Illusion zu vermeiden, dass die Ausgabe stabil ist, führen wir absichtlich geringfügige Unterschiede ein, damit Byte-für-Byte-Vergleiche wahrscheinlich fehlschlagen.
Um ein gewisses Maß an Ausgabe-Stabilität zu erreichen, empfehlen wir, die Ausgabe durch einen JSON-Formatter laufen zu lassen.
Warum ändert sich die Ausgabe von prototext ständig?
Wir geben keine Garantien für die Langzeitstabilität der Go-Implementierung des Textformats. Es gibt keine kanonische Spezifikation des Protokollpuffer-Textformats, und wir möchten uns die Möglichkeit bewahren, in Zukunft Verbesserungen an der Ausgabe des prototext-Pakets vorzunehmen. Da wir keine Stabilität der Ausgabe des Pakets garantieren, haben wir bewusst Instabilität eingeführt, um Benutzer davon abzuhalten, sich darauf zu verlassen.
Um ein gewisses Maß an Stabilität zu erreichen, empfehlen wir, die Ausgabe von prototext durch das Programm txtpbfmt laufen zu lassen. Der Formatierer kann direkt in Go über parser.Format aufgerufen werden.
Sonstiges
Wie verwende ich eine Protokollpuffer-Nachricht als Hash-Schlüssel?
Sie benötigen eine kanonische Serialisierung, bei der die gemarshallte Ausgabe einer Protokollpuffer-Nachricht über die Zeit stabil garantiert ist. Leider gibt es derzeit keine Spezifikation für eine kanonische Serialisierung. Sie müssen Ihre eigene schreiben oder einen Weg finden, sie zu vermeiden.
Kann ich der Go-Implementierung von Protokollpuffern ein neues Feature hinzufügen?
Vielleicht. Wir freuen uns immer über Vorschläge, aber wir sind sehr vorsichtig, neue Dinge hinzuzufügen.
Die Go-Implementierung von Protokollpuffern bemüht sich, mit den anderen Sprachimplementierungen übereinzustimmen. Daher scheuen wir uns oft vor Features, die zu sehr auf Go spezialisiert sind. Go-spezifische Features behindern das Ziel von Protokollpuffern als sprachunabhängiges Datenaustauschformat.
Wenn Ihre Idee nicht spezifisch für die Go-Implementierung ist, treten Sie der Diskussionsgruppe für Protokollpuffer bei und schlagen Sie sie dort vor.
Wenn Sie eine Idee für die Go-Implementierung haben, erstellen Sie ein Issue auf unserem Issue-Tracker: https://github.com/golang/protobuf/issues
Kann ich eine Option zu Marshal oder Unmarshal hinzufügen, um sie anzupassen?
Nur, wenn diese Option in anderen Implementierungen (z. B. C++, Java) existiert. Die Kodierung von Protokollpuffern (Binär-, JSON- und Textformat) muss über Implementierungen hinweg konsistent sein, damit ein in einer Sprache geschriebenes Programm Nachrichten lesen kann, die von einer anderen Sprache geschrieben wurden.
Wir werden keine Optionen zur Go-Implementierung hinzufügen, die die von Marshal-Funktionen ausgegebenen oder von Unmarshal-Funktionen gelesenen Daten beeinflussen, es sei denn, eine entsprechende Option existiert in mindestens einer anderen unterstützten Implementierung.
Kann ich den von protoc-gen-go generierten Code anpassen?
Im Allgemeinen nein. Protokollpuffer sind als sprachunabhängiges Datenaustauschformat gedacht, und implementierungsspezifische Anpassungen laufen diesem Zweck zuwider.