Поведение Enum
Объясняет, как enums работают в Protocol Buffers в настоящее время и как они должны работать.
Перечисления (enums) ведут себя по-разному в различных языковых библиотеках. Эта тема охватывает различные поведения, а также планы по переходу protobufs к состоянию, где они будут согласованы во всех языках. Если вы ищете информацию об использовании enums в целом, см. соответствующие разделы в руководствах по языку proto2, proto3 и редакциях 2023.
Определения
Enums имеют два различных варианта (открытые и закрытые). Они ведут себя идентично, за исключением обработки неизвестных значений. Практически это означает, что простые случаи работают одинаково, но некоторые крайние случаи имеют интересные последствия.
Для целей объяснения предположим, что у нас есть следующий файл .proto (мы намеренно не указываем, является ли это файлом syntax = "proto2", syntax = "proto3" или edition = "2023" прямо сейчас):
enum Enum {
A = 0;
B = 1;
}
message Msg {
optional Enum enum = 1;
}
Различие между открытыми и закрытыми можно encapsulate одним вопросом:
Что происходит, когда программа разбирает двоичные данные, содержащие поле 1 со значением
2?
- Открытые enums разберут значение
2и сохранят его непосредственно в поле. Аксессоры сообщат, что поле установлено, и вернут что-то, представляющее2. - Закрытые enums разберут значение
2и сохранят его в наборе неизвестных полей сообщения. Аксессоры сообщат, что поле не установлено, и вернут значение enum по умолчанию.
Последствия Закрытых Enums
Поведение закрытых enums имеет неожиданные последствия при разборе повторяющегося поля. Когда поле repeated Enum разбирается, все неизвестные значения будут помещены в набор неизвестных полей. При сериализации эти неизвестные значения будут записаны снова, но не на своем исходном месте в списке. Например, для файла .proto:
enum Enum {
A = 0;
B = 1;
}
message Msg {
repeated Enum r = 1;
}
Формат провода, содержащий значения [0, 2, 1, 2] для поля 1, будет разобран так, что повторяющееся поле содержит [0, 1], а значение [2, 2] окажется сохраненным как неизвестное поле. После повторной сериализации сообщения формат провода будет соответствовать [0, 1, 2, 2].
Аналогично, карты с закрытыми enums в качестве их значения будут помещать целые записи (ключ и значение) в неизвестные поля, когда значение неизвестно.
История
До введения syntax = "proto3" все enums были закрытыми. Proto3 и редакции используют открытые enums именно из-за неожиданного поведения, которое вызывают закрытые enums. Вы можете использовать features.enum_type, чтобы явно установить enums в редакциях как открытые, если это необходимо.
Спецификация
Следующее определяет поведение соответствующих спецификации реализаций для protobuf. Поскольку это тонкий момент, многие реализации не соответствуют спецификации. См. Известные проблемы для подробностей о том, как ведут себя различные реализации.
- Когда файл
proto2импортирует enum, определенный в файлеproto2, этот enum должен рассматриваться как закрытый. - Когда файл
proto3импортирует enum, определенный в файлеproto3, этот enum должен рассматриваться как открытый. - Когда файл
proto3импортирует enum, определенный в файлеproto2, компиляторprotocвыдаст ошибку. - Когда файл
proto2импортирует enum, определенный в файлеproto3, этот enum должен рассматриваться как открытый.
Редакции соблюдают то поведение, которое enum имел в файле, из которого происходит импорт. Enums proto2 всегда рассматриваются как закрытые, enums proto3 всегда рассматриваются как открытые, а при импорте из другого файла редакций используется настройка функции.
Известные проблемы
C++
Все известные выпуски C++ не соответствуют спецификации. Когда файл proto2 импортирует enum, определенный в файле proto3, C++ рассматривает это поле как закрытый enum. В редакциях это поведение представлено устаревшей функцией поля features.(pb.cpp).legacy_closed_enum. Есть два варианта перехода к соответствующему поведению:
- Удалить функцию поля. Это рекомендуемый подход, но он может вызвать изменения поведения во время выполнения. Без функции нераспознанные целые числа окажутся сохраненными в поле, приведенном к типу enum, вместо того чтобы быть помещенными в набор неизвестных полей.
- Изменить enum на закрытый. Это не рекомендуется и может вызвать изменения поведения во время выполнения, если кто-либо еще использует этот enum. Нераспознанные целые числа окажутся в наборе неизвестных полей вместо этих полей.
C#
Все известные выпуски C# не соответствуют спецификации. C# рассматривает все enums как открытые.
Java
Все известные выпуски Java не соответствуют спецификации. Когда файл proto2 импортирует enum, определенный в файле proto3, Java рассматривает это поле как закрытый enum.
В редакциях это поведение представлено устаревшей функцией поля features.(pb.java).legacy_closed_enum). Есть два варианта перехода к соответствующему поведению:
- Удалить функцию поля. Это может вызвать изменения поведения во время выполнения. Без функции нераспознанные целые числа окажутся сохраненными в поле, и метод getter enum вернет значение
UNRECOGNIZED. До этого эти значения помещались в набор неизвестных полей. - Изменить enum на закрытый. Если кто-либо еще использует его, они могут увидеть изменения поведения во время выполнения. Нераспознанные целые числа окажутся в наборе неизвестных полей вместо этих полей.
ПРИМЕЧАНИЕ: Обработка открытых enums в Java имеет удивительные крайние случаи. Для следующих определений:
syntax = "proto3"; enum Enum { A = 0; B = 1; } message Msg { repeated Enum name = 1; }Java сгенерирует методы
Enum getName()иint getNameValue(). МетодgetNameбудет возвращатьEnum.UNRECOGNIZEDдля значений вне известного набора (таких как2), тогда какgetNameValueвернет2.Аналогично, Java сгенерирует методы
Builder setName(Enum value)иBuilder setNameValue(int value). МетодsetNameбудет выбрасывать исключение при передачеEnum.UNRECOGNIZED, тогда какsetNameValueпримет2.
Kotlin
Все известные выпуски Kotlin не соответствуют спецификации. Когда файл proto2 импортирует enum, определенный в файле proto3, Kotlin рассматривает это поле как закрытый enum.
Kotlin построен на Java и разделяет все ее особенности.
Go
Все известные выпуски Go не соответствуют спецификации. Go рассматривает все enums как открытые.
JSPB
Все известные выпуски JSPB не соответствуют спецификации. JSPB рассматривает все enums как открытые.
PHP
PHP соответствует спецификации.
Python
Python соответствует спецификации в версиях выше 4.22.0 (выпущен в 2023 Q1).
Старые версии, которые больше не поддерживаются, не соответствуют спецификации. Когда файл proto2 импортирует enum, определенный в файле proto3,
Ruby
Все известные выпуски Ruby не соответствуют спецификации. Ruby рассматривает все enums как открытые.
Objective-C
Objective-C соответствует спецификации в версиях выше 3.22.0 (выпущен в 2023 Q1).
Старые версии, которые больше не поддерживаются, не соответствуют спецификации. Когда файл proto2 импортирует enum, определенный в файле proto3, несоответствующие версии ObjC рассматривают это поле как закрытый enum.
Swift
Swift соответствует спецификации.
Dart
Dart рассматривает все enums как закрытые.