Enum Verhalten

Erklärt, wie Enums derzeit in Protocol Buffers funktionieren und wie sie funktionieren sollten.

Enums verhalten sich in verschiedenen Sprachbibliotheken unterschiedlich. Dieses Thema behandelt die verschiedenen Verhaltensweisen sowie die Pläne, Protobufs in einen Zustand zu bringen, der über alle Sprachen hinweg konsistent ist. Wenn Sie nach Informationen zur allgemeinen Verwendung von Enums suchen, siehe die entsprechenden Abschnitte in den Sprachleitfäden für proto2, proto3 und editions 2023.

Definitionen

Enums haben zwei verschiedene Varianten (offen und geschlossen). Sie verhalten sich identisch, außer bei der Behandlung unbekannter Werte. Praktisch bedeutet dies, dass einfache Fälle gleich funktionieren, aber einige Eckfälle interessante Auswirkungen haben.

Zur Erklärung nehmen wir an, wir haben die folgende .proto-Datei (wir geben bewusst nicht an, ob es sich um eine Datei mit syntax = "proto2", syntax = "proto3" oder edition = "2023" handelt)

enum Enum {
  A = 0;
  B = 1;
}

message Msg {
  optional Enum enum = 1;
}

Die Unterscheidung zwischen offen und geschlossen kann in einer einzigen Frage zusammengefasst werden

Was passiert, wenn ein Programm Binärdaten parst, die Feld 1 mit dem Wert 2 enthalten?

  • Offene Enums parsen den Wert 2 und speichern ihn direkt im Feld. Accessoren melden, dass das Feld gesetzt ist, und geben etwas zurück, das 2 repräsentiert.
  • Geschlossene Enums parsen den Wert 2 und speichern ihn im unbekannten Feld-Set der Nachricht. Accessoren melden, dass das Feld nicht gesetzt ist, und geben den Standardwert des Enums zurück.

Auswirkungen von geschlossenen Enums

Das Verhalten von geschlossenen Enums hat unerwartete Folgen beim Parsen eines wiederholten Feldes. Wenn ein repeated Enum-Feld geparst wird, werden alle unbekannten Werte in das unbekannte Feld-Set platziert. Wenn es serialisiert wird, werden diese unbekannten Werte erneut geschrieben, aber nicht an ihrer ursprünglichen Position in der Liste. Zum Beispiel, gegeben die .proto-Datei

enum Enum {
  A = 0;
  B = 1;
}

message Msg {
  repeated Enum r = 1;
}

Ein Wire-Format, das die Werte [0, 2, 1, 2] für Feld 1 enthält, wird so geparst, dass das wiederholte Feld [0, 1] enthält und der Wert [2, 2] als unbekanntes Feld gespeichert wird. Nach der erneuten Serialisierung der Nachricht entspricht das Wire-Format [0, 1, 2, 2].

Ähnlich werden Maps mit geschlossenen Enums als Wert ganze Einträge (Schlüssel und Wert) in die unbekannten Felder platzieren, wenn der Wert unbekannt ist.

Historie

Vor der Einführung von syntax = "proto3" waren alle Enums geschlossen. Proto3 und Editions verwenden offene Enums gerade wegen des unerwarteten Verhaltens, das geschlossene Enums verursachen. Sie können features.enum_type verwenden, um Editions-Enums explizit als offen zu setzen, falls erforderlich.

Spezifikation

Das Folgende spezifiziert das Verhalten konformer Implementierungen für Protobuf. Da dies subtil ist, sind viele Implementierungen nicht konform. Siehe Bekannte Probleme für Details, wie sich verschiedene Implementierungen verhalten.

  • Wenn eine proto2-Datei ein Enum importiert, das in einer proto2-Datei definiert ist, sollte dieses Enum als geschlossen behandelt werden.
  • Wenn eine proto3-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, sollte dieses Enum als offen behandelt werden.
  • Wenn eine proto3-Datei ein Enum importiert, das in einer proto2-Datei definiert ist, gibt der protoc-Compiler einen Fehler aus.
  • Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, sollte dieses Enum als offen behandelt werden.

Editions respektieren das Verhalten, das das Enum in der importierten Datei hatte. Proto2-Enums werden immer als geschlossen behandelt, Proto3-Enums immer als offen, und beim Importieren aus einer anderen Editions-Datei wird die Feature-Einstellung verwendet.

Bekannte Probleme

C++

Alle bekannten C++-Releases sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandelt C++ dieses Feld als geschlossenes Enum. Unter Editions wird dieses Verhalten durch das veraltete Feld-Feature features.(pb.cpp).legacy_closed_enum dargestellt. Es gibt zwei Optionen für ein konformes Verhalten

  • Entfernen Sie das Feld-Feature. Dies ist der empfohlene Ansatz, kann aber zu Laufzeitänderungen führen. Ohne das Feature werden nicht erkannte ganze Zahlen in das Feld gespeichert, das in den Enum-Typ gecastet wird, anstatt in das unbekannte Feld-Set zu gelangen.
  • Ändern Sie das Enum auf geschlossen. Dies wird abgeraten und kann zu Laufzeitänderungen führen, wenn irgendjemand anderes das Enum verwendet. Nicht erkannte ganze Zahlen gelangen anstelle dieser Felder in das unbekannte Feld-Set.

C#

Alle bekannten C#-Releases sind nicht konform. C# behandelt alle Enums als offen.

Java

Alle bekannten Java-Releases sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandelt Java dieses Feld als geschlossenes Enum.

Unter Editions wird dieses Verhalten durch das veraltete Feld-Feature features.(pb.java).legacy_closed_enum dargestellt). Es gibt zwei Optionen für ein konformes Verhalten

  • Entfernen Sie das Feld-Feature. Dies kann zu Laufzeitänderungen führen. Ohne das Feature werden nicht erkannte ganze Zahlen im Feld gespeichert und der Wert UNRECOGNIZED wird vom Enum-Getter zurückgegeben. Zuvor gelangten diese Werte in das unbekannte Feld-Set.
  • Ändern Sie das Enum auf geschlossen. Wenn irgendjemand anderes es verwendet, kann es zu Laufzeitänderungen kommen. Nicht erkannte ganze Zahlen gelangen anstelle dieser Felder in das unbekannte Feld-Set.

HINWEIS: Javabehandlung von offenen Enums hat überraschende Randfälle. Angesichts der folgenden Definitionen

syntax = "proto3";

enum Enum {
  A = 0;
  B = 1;
}

message Msg {
  repeated Enum name = 1;
}

Java generiert die Methoden Enum getName() und int getNameValue(). Die Methode getName gibt Enum.UNRECOGNIZED für Werte außerhalb des bekannten Satzes zurück (wie z. B. 2), während getNameValue 2 zurückgibt.

Ähnlich generiert Java die Methoden Builder setName(Enum value) und Builder setNameValue(int value). Die Methode setName löst eine Ausnahme aus, wenn Enum.UNRECOGNIZED übergeben wird, während setNameValue 2 akzeptiert.

Kotlin

Alle bekannten Kotlin-Releases sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandelt Kotlin dieses Feld als geschlossenes Enum.

Kotlin basiert auf Java und teilt all dessen Eigenheiten.

Go

Alle bekannten Go-Releases sind nicht konform. Go behandelt alle Enums als offen.

JSPB

Alle bekannten JSPB-Releases sind nicht konform. JSPB behandelt alle Enums als offen.

PHP

PHP ist konform.

Python

Python ist ab Version 4.22.0 (veröffentlicht 2023 Q1) konform.

Ältere, nicht mehr unterstützte Versionen sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandeln nicht-konforme Python-Versionen dieses Feld als geschlossenes Enum.

Ruby

Alle bekannten Ruby-Releases sind nicht konform. Ruby behandelt alle Enums als offen.

Objective-C

Objective-C ist ab Version 3.22.0 (veröffentlicht 2023 Q1) konform.

Ältere, nicht mehr unterstützte Versionen sind nicht konform. Wenn eine proto2-Datei ein Enum importiert, das in einer proto3-Datei definiert ist, behandeln nicht-konforme ObjC-Versionen dieses Feld als geschlossenes Enum.

Swift

Swift ist konform.

Dart

Dart behandelt alle Enums als geschlossen.