Обзор редакций Protobuf

Редакции Protobuf заменяют обозначения proto2 и proto3, которые мы использовали для Protocol Buffers. Вместо добавления syntax = "proto2" или syntax = "proto3" в начале файлов определений proto вы используете номер редакции, например edition = "2024", чтобы указать поведение по умолчанию для вашего файла. Редакции позволяют языку развиваться постепенно с течением времени.

Вместо жестко заданного поведения, которое было в старых версиях, редакции представляют собой набор функций (features) со значением (поведением) по умолчанию для каждой функции. Функции — это опции на уровне файла, сообщения, поля, перечисления и т.д., которые определяют поведение protoc, генераторов кода и сред выполнения protobuf. Вы можете явно переопределить поведение на этих различных уровнях (файл, сообщение, поле, ...), когда ваши потребности не совпадают с поведением по умолчанию для выбранной вами редакции. Вы также можете переопределять свои собственные переопределения. Раздел ниже в этой теме о лексической области видимости более подробно освещает этот вопрос.

ПРИМЕЧАНИЕ: Последняя выпущенная редакция — 2024.

Жизненный цикл функции

Редакции предоставляют фундаментальные этапы для жизненного цикла функции. У функций есть ожидаемый жизненный цикл: введение, изменение поведения по умолчанию, устаревание и последующее удаление. Например:

  1. Редакция 2031 создает feature.amazing_new_feature со значением по умолчанию false. Это значение сохраняет то же поведение, что и все предыдущие редакции. То есть по умолчанию воздействия нет. Не все новые функции будут по умолчанию иметь no-op опцию, но для этого примера amazing_new_feature имеет.

  2. Разработчики обновляют свои .proto файлы до edition = "2031".

  3. Более поздняя редакция, например редакция 2033, меняет значение по умолчанию для feature.amazing_new_feature с false на true. Это желаемое поведение для всех protobuf, и причина, по которой команда protobuf создала эту функцию.

    Использование инструмента Prototiller для миграции более ранних версий proto-файлов в редакцию 2033 добавляет явные записи feature.amazing_new_feature = false по мере необходимости, чтобы продолжить сохранять предыдущее поведение. Разработчики удаляют эти вновь добавленные настройки, когда хотят, чтобы новое поведение применялось к их .proto файлам.

  1. В какой-то момент feature.amazing_new_feature помечается как устаревшая в одной редакции и удаляется в более поздней.

    Когда функция удаляется, генераторы кода для этого поведения и библиотеки времени выполнения, которые его поддерживают, также могут быть удалены. Сроки будут щедрыми, однако. Следуя примеру на предыдущих этапах жизненного цикла, устаревание может произойти в редакции 2034, но удаление не произойдет до редакции 2036, примерно двумя годами позже. Удаление функции всегда будет инициировать увеличение мажорной версии.

У вас будет полное время миграции Google плюс период устаревания для обновления вашего кода.

Предыдущий пример жизненного цикла использовал булевы значения для функций, но функции также могут использовать перечисления. Например, features.field_presence имеет значения LEGACY_REQUIRED, EXPLICIT и IMPLICIT.

Миграция на редакции Protobuf

Редакции не нарушат работу существующих бинарных файлов и не изменят бинарный, текстовый или JSON формат сериализации сообщения. Редакция 2023 была настолько минимально разрушительной, насколько это возможно. Она установила базовый уровень и объединила определения proto2 и proto3 в новый единый формат определений.

По мере выпуска новых редакций поведение по умолчанию для функций может изменяться. Вы можете поручить Prototiller выполнить no-op преобразование вашего .proto файла или вы можете выбрать принятие некоторых или всех новых поведений. Планируется, что редакции будут выпускаться примерно раз в год.

С Proto2 на редакции

В этом разделе показан файл proto2 и то, как он может выглядеть после запуска инструмента Prototiller для изменения файлов определений на использование синтаксиса редакций Protobuf.

Синтаксис Proto2

// файл proto2
syntax = "proto2";

package com.example;

message Player {
  // в proto2 optional поля имеют явное присутствие
  optional string name = 1 [default = "N/A"];
  // proto2 все еще поддерживает проблемное правило поля "required"
  required int32 id = 2;
  // в proto2 по умолчанию это не упаковано (packed)
  repeated int32 scores = 3;

  enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  // в proto2 перечисления закрыты (closed)
  optional Handed handed = 4;

  reserved "gender";
}

Синтаксис редакций

// Версия файла proto2 в редакциях
edition = "2024";

package com.example;

option features.utf8_validation = NONE;
option features.enforce_naming_style = STYLE_LEGACY;
option features.default_symbol_visibility = EXPORT_ALL;

// Устанавливает поведение по умолчанию для строк C++
option features.(pb.cpp).string_type = STRING;

message Player {
  // поля имеют явное присутствие, поэтому явная настройка не нужна
  string name = 1 [default = "N/A"];
  // чтобы соответствовать поведению proto2, LEGACY_REQUIRED установлен на уровне поля
  int32 id = 2 [features.field_presence = LEGACY_REQUIRED];
  // чтобы соответствовать поведению proto2, EXPANDED установлен на уровне поля
  repeated int32 scores = 3 [features.repeated_field_encoding = EXPANDED];

  export enum Handed {
    // это переопределяет поведение редакций по умолчанию, которое OPEN
    option features.enum_type = CLOSED;
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  Handed handed = 4;

  reserved gender;
}

С Proto3 на редакции

В этом разделе показан файл proto3 и то, как он может выглядеть после запуска инструмента Prototiller для изменения файлов определений на использование синтаксиса редакций Protobuf.

Синтаксис Proto3

// файл proto3
syntax = "proto3";

package com.example;

message Player {
  // в proto3 optional поля имеют явное присутствие
  optional string name = 1;
  // в proto3 правило поля не указано, по умолчанию неявное присутствие
  int32 id = 2;
  // в proto3 по умолчанию это упаковано (packed)
  repeated int32 scores = 3;

  enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  // в proto3 перечисления открыты (open)
  optional Handed handed = 4;

  reserved "gender";
}

Синтаксис редакций

// Версия файла proto3 в редакциях
edition = "2024";

package com.example;

option features.utf8_validation = NONE;
option features.enforce_naming_style = STYLE_LEGACY;
option features.default_symbol_visibility = EXPORT_ALL;

// Устанавливает поведение по умолчанию для строк C++
option features.(pb.cpp).string_type = STRING;

message Player {
  // поля имеют явное присутствие, поэтому явная настройка не нужна
  string name = 1 [default = "N/A"];
  // чтобы соответствовать поведению proto3, IMPLICIT установлен на уровне поля
  int32 id = 2 [features.field_presence = IMPLICIT];
  // PACKED является состоянием по умолчанию и приведено только для иллюстрации
  repeated int32 scores = 3 [features.repeated_field_encoding = PACKED];

  export enum Handed {
    HANDED_UNSPECIFIED = 0;
    HANDED_LEFT = 1;
    HANDED_RIGHT = 2;
    HANDED_AMBIDEXTROUS = 3;
  }

  Handed handed = 4;

  reserved gender;
}

Лексическая область видимости

Синтаксис редакций поддерживает лексическую область видимости с разрешенным для каждой функции списком целей. Например, в Редакции 2023 функции могут быть указаны только на уровне файла или на самом низком уровне детализации. Реализация лексической области видимости позволяет вам установить поведение по умолчанию для функции во всем файле, а затем переопределить это поведение на уровне сообщения, поля, перечисления, значения перечисления, oneof, сервиса или метода. Настройки, сделанные на более высоком уровне (файл, сообщение), применяются, когда никакая настройка не сделана в той же области видимости (поле, значение перечисления). Любые функции, не установленные явно, соответствуют поведению, определенному в версии редакции, используемой для .proto файла.

Следующий пример кода показывает некоторые функции, установленные на уровне файла, поля и перечисления.

edition = "2024";

option features.enum_type = CLOSED;

message Person {
  string name = 1;
  int32 id = 2 [features.field_presence = IMPLICIT];

  enum Pay_Type {
    PAY_TYPE_UNSPECIFIED = 1;
    PAY_TYPE_SALARY = 2;
    PAY_TYPE_HOURLY = 3;
  }

  enum Employment {
    option features.enum_type = OPEN;
    EMPLOYMENT_UNSPECIFIED = 0;
    EMPLOYMENT_FULLTIME = 1;
    EMPLOYMENT_PARTTIME = 2;
  }
  Employment employment = 4;
}

В предыдущем примере функция присутствия установлена в IMPLICIT; она бы по умолчанию была EXPLICIT, если бы не была установлена. enum Pay_Type будет CLOSED, так как он применяет настройку на уровне файла. Однако enum Employment будет OPEN, так как он установлен внутри перечисления.

Prototiller

Когда инструмент Prototiller будет запущен, мы предоставим как руководство по миграции, так и инструменты миграции для облегчения перехода на редакции и между ними. Инструмент позволит вам:

  • конвертировать файлы определений proto2 и proto3 в новый синтаксис редакций, в больших масштабах
  • мигрировать файлы с одной редакции на другую
  • манипулировать proto-файлами другими способами

Обратная совместимость

Мы создаем редакции Protobuf максимально ненарушающими. Например, вы можете импортировать определения proto2 и proto3 в файлы определений на основе редакций, и наоборот:

// файл myproject/foo.proto
syntax = "proto2";

enum Employment {
  EMPLOYMENT_UNSPECIFIED = 0;
  EMPLOYMENT_FULLTIME = 1;
  EMPLOYMENT_PARTTIME = 2;
}
// файл myproject/edition.proto
edition = "2024";

import "myproject/foo.proto";

Хотя сгенерированный код изменяется при переходе с proto2 или proto3 на редакции, бинарный формат (wire format) не меняется. Вы все равно сможете получать доступ к файлам данных proto2 и proto3 или потокам файлов, используя ваши proto-определения с синтаксисом редакций.

Изменения грамматики

В редакциях по сравнению с proto2 и proto3 есть некоторые изменения грамматики.

Описание синтаксиса

Вместо элемента syntax вы используете элемент edition:

syntax = "proto2";
syntax = "proto3";
edition = "2028";

Зарезервированные имена

Вам больше не нужно заключать имена полей и значений перечислений в кавычки при их резервировании:

reserved foo, bar;

Синтаксис Group

Синтаксис Group, доступный в proto2, удален в редакциях. Специальный бинарный формат, который использовали groups, все еще доступен через использование кодировки сообщений DELIMITED.

Метка Required

Метка required, доступная только в proto2, недоступна в редакциях. Базовая функциональность все еще доступна через использование features.field_presence=LEGACY_REQUIRED.

import option

Редакция 2024 добавила поддержку импорта опций с использованием синтаксиса import option.

Импорты опций должны следовать после любых других операторов import.

В отличие от обычных операторов import, import option импортирует только пользовательские опции, определенные в .proto файле, без импорта других символов.

Это означает, что сообщения и перечисления исключены из импорта опций. В следующем примере сообщение Bar не может быть использовано как тип поля в foo.proto, но опции с типом Bar все еще могут быть установлены.

// bar.proto
edition = "2024";

import "google/protobuf/descriptor.proto";

message Bar {
  bool bar = 1;
}

extend proto2.FileOptions {
  bool file_opt1 = 5000;
  Bar file_opt2 = 5001;
}

// foo.proto:
edition = "2024";

import option "bar.proto";

option (file_opt1) = true;
option (file_opt2) = {bar: true};

message Foo {
  // Bar bar = 1; // Это не разрешено
}

Импорты опций не требуют сгенерированного кода для своих символов и, следовательно, должны указываться как option_deps в proto_library вместо deps. Это позволяет избежать генерации недостижимого кода.

proto_library(
  name = "foo",
  srcs = ["foo.proto"],
  option_deps = [":custom_option_proto"]
)

Импорты опций и option_deps настоятельно рекомендуются при импорте функций языка protobuf и других пользовательских опций, чтобы избежать генерации ненужного кода.

Это заменяет import weak, который был удален в Редакции 2024.

Ключевые слова export / local

Ключевые слова export и local были добавлены в Редакции 2024 в качестве модификаторов видимости символов, которые можно импортировать, вместо поведения по умолчанию, заданного features.default_symbol_visibility.

Это управляет тем, какие символы могут быть импортированы из других proto-файлов, но не влияет на генерацию кода.

В Редакции 2024 они могут быть установлены для всех символов message и enum по умолчанию. Однако некоторые значения функции default_symbol_visibility дополнительно ограничивают, какие символы можно экспортировать.

Пример:

// Символы верхнего уровня экспортируются по умолчанию в Редакции 2024
message LocalMessage {
  int32 baz = 1;
  // Вложенные символы являются локальными по умолчанию в Редакции 2024; применение ключевого слова `export`
  // переопределяет это
  export enum ExportedNestedEnum {
    UNKNOWN_EXPORTED_NESTED_ENUM_VALUE = 0;
  }
}

// Ключевое слово `local` переопределяет поведение по умолчанию для экспорта сообщений
local message AnotherMessage {
  int32 foo = 1;
}

import weak и опция поля Weak

Начиная с Редакции 2024, слабый импорт (weak imports) больше не разрешен.

Если вы ранее полагались на import weak для объявления "слабой зависимости"—чтобы импортировать пользовательские опции без сгенерированного кода для C++ и Go—вам следует вместо этого перейти на использование import option.

См. подробности в разделе import option.

Опция поля ctype

Начиная с Редакции 2024, опция поля ctype больше не разрешена. Используйте вместо нее функцию string_type.

См. подробности features.(pb.cpp).string_type.

Опция файла java_multiple_files

Начиная с Редакции 2024, опция файла java_multiple_files больше недоступна. Вместо нее используйте функцию Java features.(pb.java).nest_in_file_class.