ПРИМЕЧАНИЕ. Этот сайт устарел. Сайт будет отключен после 31 января 2023 года, и трафик будет перенаправлен на новый сайт по адресу https://protobuf.dev . А пока обновления будут производиться только для protobuf.dev.

Перейти сгенерированный код

Оптимизируйте свои подборки Сохраняйте и классифицируйте контент в соответствии со своими настройками.

На этой странице точно описывается, какой код Go генерирует компилятор буфера протокола для любого заданного определения протокола. Любые различия между сгенерированным кодом proto2 и proto3 выделены — обратите внимание, что эти различия заключаются в сгенерированном коде, как описано в этом документе, а не в базовом API, который одинаков в обеих версиях. Вы должны прочитать руководство по языку proto2 и/или руководство по языку proto3, прежде чем читать этот документ.

Вызов компилятора

Компилятору буфера протокола требуется плагин для генерации кода Go. Установите его с помощью Go 1.16 или выше, запустив:

go install google.golang.org/protobuf/cmd/protoc-gen-go@latest

Это установит protoc-gen-go в $GOBIN . Задайте переменную среды $GOBIN , чтобы изменить место установки. Он должен находиться в вашем $PATH , чтобы компилятор буфера протокола мог его найти.

Компилятор буфера протокола производит выходные данные Go при вызове с флагом go_out . Аргумент флага go_out — это каталог, в который вы хотите, чтобы компилятор записал вывод Go. Компилятор создает один исходный файл для каждого входного файла .proto . Имя выходного файла создается путем замены расширения .proto на .pb.go .

Расположение в выходном каталоге сгенерированного файла .pb.go зависит от флагов компилятора. Есть несколько режимов вывода:

  • Если указан флаг paths=import , выходной файл помещается в каталог, имя которого соответствует пути импорта пакета Go. Например, входной файл protos/buzz.proto с путем импорта Go в example.com/project/protos/fizz приводит к выходному файлу по адресу example.com/project/protos/fizz/buzz.pb.go . Это режим вывода по умолчанию, если флаг paths не указан.
  • Если указан флаг module=$PREFIX , выходной файл помещается в каталог, названный в честь пути импорта пакета Go, но указанный префикс каталога удаляется из имени выходного файла. Например, входной файл protos/buzz.proto с путем импорта Go, например example.com/project/protos/fizz и example.com/project , указанным в качестве префикса module , приводит к выходному файлу protos/fizz/buzz.pb.go . Создание любых пакетов Go за пределами пути к модулю приводит к ошибке. Этот режим полезен для вывода сгенерированных файлов непосредственно в модуль Go.
  • Если указан флаг paths=source_relative , выходной файл помещается в тот же относительный каталог, что и входной файл. Например, входной файл protos/buzz.proto приводит к выходному файлу protos/buzz.pb.go .

Флаги, характерные для protoc-gen-go , предоставляются путем передачи флага go_opt при вызове protoc . Можно передать несколько флагов go_opt . Например, при запуске:

protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto

компилятор будет читать входные файлы foo.proto и bar/baz.proto из каталога src и записывать выходные файлы foo.pb.go и bar/baz.pb.go out выходной каталог. Компилятор автоматически создает вложенные выходные подкаталоги, если это необходимо, но сам выходной каталог не создает.

Пакеты

Чтобы сгенерировать код Go, путь импорта пакета Go должен быть указан для каждого файла .proto (включая те, от которых транзитивно зависят создаваемые файлы .proto ). Есть два способа указать путь импорта Go:

  • объявив его в файле .proto , или
  • объявив его в командной строке при вызове protoc .

Мы рекомендуем объявить его в файле .proto , чтобы пакеты Go для файлов .proto можно было централизованно идентифицировать с самими файлами .proto и упростить набор флагов, передаваемых при вызове protoc . Если путь импорта Go для данного файла .proto как в самом файле .proto , так и в командной строке, то последний имеет приоритет над первым.

Путь импорта Go локально указывается в файле .proto путем объявления опции go_package с полным путем импорта пакета Go. Пример использования:

option go_package = "example.com/project/protos/fizz";

Путь импорта Go можно указать в командной строке при вызове компилятора, передав один или несколько флагов M${PROTO_FILE}=${GO_IMPORT_PATH} . Пример использования:

protoc --proto_path=src \
  --go_opt=Mprotos/buzz.proto=example.com/project/protos/fizz \
  --go_opt=Mprotos/bar.proto=example.com/project/protos/foo \
  protos/buzz.proto protos/bar.proto

Поскольку сопоставление всех файлов .proto с их путями импорта Go может быть довольно большим, этот режим указания путей импорта Go обычно выполняется каким-либо инструментом сборки (например, Bazel ), который контролирует все дерево зависимостей. Если для данного файла .proto есть повторяющиеся записи, приоритет имеет последняя указанная запись.

И для параметра go_package , и для флага M значение может включать явное имя пакета, отделенное от пути импорта точкой с запятой. Например: "example.com/protos/foo;package_name" . Такое использование не рекомендуется, так как имя пакета будет получено по умолчанию из пути импорта разумным образом.

Путь импорта используется для определения того, какие операторы импорта должны создаваться, когда один файл .proto импортирует другой файл .proto . Например, если a.proto импортирует b.proto , то сгенерированный файл a.pb.go должен импортировать пакет Go, который содержит сгенерированный файл b.pb.go (если только оба файла не находятся в одном пакете). Путь импорта также используется для создания имен выходных файлов. Дополнительные сведения см. в разделе «Вызов компилятора» выше.

Между путем импорта Go и спецификатором package в файле .proto нет корреляции. Последнее относится только к пространству имен protobuf, а первое относится только к пространству имен Go. Кроме того, нет корреляции между путем импорта Go и путем импорта .proto .

Сообщения

Учитывая простое объявление сообщения:

message Foo {}

компилятор буфера протокола генерирует структуру с именем Foo . *Foo реализует интерфейс proto.Message .

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

Интерфейс proto.Message определяет метод ProtoReflect . Этот метод возвращает protoreflect.Message , который обеспечивает представление сообщения на основе отражения.

Параметр « optimize_for » не влияет на вывод генератора кода Go.

Вложенные типы

Сообщение может быть объявлено внутри другого сообщения. Например:

message Foo {
  message Bar {
  }
}

В этом случае компилятор генерирует две структуры: Foo и Foo_Bar .

Поля

Компилятор буфера протокола создает поле структуры для каждого поля, определенного в сообщении. Точная природа этого поля зависит от его типа и от того, является ли оно единичным, повторяющимся, отображением или одним из полей.

Обратите внимание, что сгенерированные имена полей Go всегда используют верблюжий регистр имен, даже если имя поля в файле .proto использует нижний регистр с подчеркиванием ( как и должно быть ). Преобразование регистра работает следующим образом:

  1. Первая буква заглавная для экспорта. Если первый символ является символом подчеркивания, он удаляется и добавляется заглавная буква X.
  2. Если за внутренним подчеркиванием следует строчная буква, подчеркивание удаляется, а следующая буква становится заглавной.

Таким образом, foo_bar_baz становится FooBarBaz в Go, а _my_field_name_2 становится XMyFieldName_2 .

Сингулярные скалярные поля (proto2)

Для любого из этих определений полей:

optional int32 foo = 1;
required int32 foo = 1;

компилятор создает структуру с полем *int32 с именем Foo и методом GetFoo() , который возвращает значение int32 в Foo или значение по умолчанию, если поле не задано. Если значение по умолчанию не задано явно, вместо него используется нулевое значение этого типа ( 0 для чисел, пустая строка для строк).

Для других типов скалярных полей (включая bool , bytes и string ) *int32 заменяется соответствующим типом Go в соответствии с таблицей типов скалярных значений .

Сингулярные скалярные поля (proto3)

Для этого определения поля:

int32 foo = 1;
Компилятор сгенерирует структуру с полем int32 с именем Foo и методом GetFoo() , который возвращает значение int32 в Foo или нулевое значение этого типа, если поле не задано ( 0 для чисел , пустая строка для строк).

Для других типов скалярных полей (включая bool , bytes и string ) int32 заменяется соответствующим типом Go в соответствии с таблицей типов скалярных значений . Неустановленные значения в прототипе будут представлены как нулевое значение этого типа ( 0 для чисел, пустая строка для строк).

Отдельные поля сообщений

Учитывая тип сообщения:

message Bar {}
Для сообщения с полем Bar :
// proto2
message Baz {
  optional Bar foo = 1;
  // The generated code is the same result if required instead of optional.
}

// proto3
message Baz {
  Bar foo = 1;
}
Компилятор создаст структуру Go
type Baz struct {
	Foo *Bar
}

Поля сообщения могут быть установлены на nil , что означает, что поле не установлено, эффективно очищая поле. Это не эквивалентно установке значения для «пустого» экземпляра структуры сообщения.

Компилятор также генерирует вспомогательную функцию func (m *Baz) GetFoo() *Bar . Эта функция возвращает nil *Bar , если m равно nil или foo не установлен. Это позволяет связывать вызовы get без промежуточных nil проверок.

Повторяющиеся поля

Каждое повторяющееся поле генерирует срез поля T в структуре в Go, где T — тип элемента поля. Для этого сообщения с повторяющимся полем:

message Baz {
  repeated Bar foo = 1;
}

компилятор генерирует структуру Go:

type Baz struct {
	Foo  []*Bar
}

Аналогично, для repeated bytes foo = 1; компилятор сгенерирует структуру Go с полем [][]byte с именем Foo . Для повторного перечисления repeated MyEnum bar = 2; , компилятор создает структуру с полем []MyEnum с именем Bar .

В следующем примере показано, как установить поле:

baz := &Baz{
  Foo: []*Bar{
    {}, // First element.
    {}, // Second element.
  },
}

Чтобы получить доступ к полю, вы можете сделать следующее:

foo := baz.GetFoo() // foo type is []*Bar.
b1 := foo[0] // b1 type is *Bar, the first element in foo.

Поля карты

Каждое поле карты создает поле в структуре типа map[TKey]TValue где TKey — тип ключа поля, а TValue — тип значения поля. Для этого сообщения с полем карты:

message Bar {}

message Baz {
  map<string, Bar> foo = 1;
}

компилятор генерирует структуру Go:

type Baz struct {
	Foo map[string]*Bar
}

одно из полей

Для поля oneof компилятор protobuf генерирует одно поле с типом интерфейса isMessageName_MyField . Он также генерирует структуру для каждого из сингулярных полей в oneof. Все они реализуют этот интерфейс isMessageName_MyField .

Для этого сообщения с полем oneof:

package account;
message Profile {
  oneof avatar {
    string image_url = 1;
    bytes image_data = 2;
  }
}

компилятор генерирует структуры:

type Profile struct {
	// Types that are valid to be assigned to Avatar:
	//	*Profile_ImageUrl
	//	*Profile_ImageData
	Avatar isProfile_Avatar `protobuf_oneof:"avatar"`
}

type Profile_ImageUrl struct {
        ImageUrl string
}
type Profile_ImageData struct {
        ImageData []byte
}

И *Profile_ImageUrl и *Profile_ImageData реализуют isProfile_Avatar , предоставляя пустой isProfile_Avatar() .

В следующем примере показано, как установить поле:

p1 := &account.Profile{
  Avatar: &account.Profile_ImageUrl{"http://example.com/image.png"},
}

// imageData is []byte
imageData := getImageData()
p2 := &account.Profile{
  Avatar: &account.Profile_ImageData{imageData},
}

Чтобы получить доступ к полю, вы можете использовать переключатель типа значения для обработки различных типов сообщений.

switch x := m.Avatar.(type) {
case *account.Profile_ImageUrl:
	// Load profile image based on URL
	// using x.ImageUrl
case *account.Profile_ImageData:
	// Load profile image based on bytes
	// using x.ImageData
case nil:
	// The field is not set.
default:
	return fmt.Errorf("Profile.Avatar has unexpected type %T", x)
}

Компилятор также генерирует методы get func (m *Profile) GetImageUrl() string и func (m *Profile) GetImageData() []byte . Каждая функция get возвращает значение для этого поля или нулевое значение, если оно не установлено.

Перечисления

Учитывая перечисление вроде:

message SearchRequest {
  enum Corpus {
    UNIVERSAL = 0;
    WEB = 1;
    IMAGES = 2;
    LOCAL = 3;
    NEWS = 4;
    PRODUCTS = 5;
    VIDEO = 6;
  }
  Corpus corpus = 1;
  ...
}

компилятор буфера протокола генерирует тип и ряд констант с этим типом.

Для перечислений внутри сообщения (например, приведенного выше) имя типа начинается с имени сообщения:

type SearchRequest_Corpus int32
.

Для перечисления на уровне пакета:

enum Foo {
  DEFAULT_BAR = 0;
  BAR_BELLS = 1;
  BAR_B_CUE = 2;
}

имя типа Go не отличается от имени прото-перечисления:

type Foo int32

Этот тип имеет метод String() , который возвращает имя заданного значения.

Метод Enum() инициализирует только что выделенную память заданным значением и возвращает соответствующий указатель:

func (Foo) Enum() *Foo
.

Если вы используете синтаксис proto3 для своего определения .proto , метод `Enum()` не создается.

Компилятор буфера протокола создает константу для каждого значения в перечислении. Для перечислений в сообщении константы начинаются с имени вложенного сообщения:

const (
	SearchRequest_UNIVERSAL SearchRequest_Corpus = 0
	SearchRequest_WEB       SearchRequest_Corpus = 1
	SearchRequest_IMAGES    SearchRequest_Corpus = 2
	SearchRequest_LOCAL     SearchRequest_Corpus = 3
	SearchRequest_NEWS      SearchRequest_Corpus = 4
	SearchRequest_PRODUCTS  SearchRequest_Corpus = 5
	SearchRequest_VIDEO     SearchRequest_Corpus = 6
)

Для перечисления на уровне пакета константы вместо этого начинаются с имени перечисления:

const (
	Foo_DEFAULT_BAR Foo = 0
	Foo_BAR_BELLS   Foo = 1
	Foo_BAR_B_CUE   Foo = 2
)
.

Компилятор protobuf также генерирует карту из целочисленных значений в имена строк и карту из имен в значения:

var Foo_name = map[int32]string{
	0: "DEFAULT_BAR",
	1: "BAR_BELLS",
	2: "BAR_B_CUE",
}
var Foo_value = map[string]int32{
	"DEFAULT_BAR": 0,
	"BAR_BELLS":   1,
	"BAR_B_CUE":   2,
}

Обратите внимание, что язык .proto позволяет нескольким символам перечисления иметь одно и то же числовое значение. Символы с одинаковым числовым значением являются синонимами. Они представлены в Go точно так же, с несколькими именами, соответствующими одному и тому же числовому значению. Обратное сопоставление содержит одну запись для числового значения имени, которое появляется первым в файле .proto.

Расширения (прото2)

Учитывая определение расширения:

extend Foo {
  optional int32 bar = 123;
}

Компилятор буфера протокола создаст значение protoreflect.ExtensionType с именем E_Bar . Это значение может использоваться с proto.GetExtension , proto.SetExtension , proto.HasExtension и proto.ClearExtension для доступа к расширению в сообщении. Функции GetExtension и SetExtension соответственно принимают и возвращают значение interface{} , содержащее тип значения расширения.

Для сингулярных скалярных полей расширения тип значения расширения — это соответствующий тип Go из таблицы типов скалярных значений .

Для отдельных полей расширений внедренных сообщений тип значения расширения — *M , где M — тип сообщения поля.

Для повторяющихся полей расширения тип значения расширения является срезом единственного типа.

Например, учитывая следующее определение:

extend Foo {
  optional int32 singular_int32 = 1;
  repeated bytes repeated_string = 2;
  optional Bar singular_message = 3;
}

Значения расширения могут быть доступны как:

m := &somepb.Foo{}
proto.SetExtension(m, extpb.E_SingularInt32, int32(1))
proto.SetExtension(m, extpb.E_RepeatedString, []string{"a", "b", "c"})
proto.SetExtension(m, extpb.E_SingularMessage, &extpb.Bar{})

v1 := proto.GetExtension(m, extpb.E_SingularInt32).(int32)
v2 := proto.GetExtension(m, extpb.E_RepeatedString).([][]byte)
v3 := proto.GetExtension(m, extpb.E_SingularMessage).(*extpb.Bar)

Расширения могут быть объявлены вложенными внутри другого типа. Например, общий шаблон — сделать что-то вроде этого:

message Baz {
  extend Foo {
    optional Baz foo_ext = 124;
  }
}

В этом случае значение ExtensionType называется E_Baz_Foo .

Услуги

Генератор кода Go по умолчанию не производит выходные данные для служб. Если вы включите подключаемый модуль gRPC (см. краткое руководство по gRPC Go ), будет сгенерирован код для поддержки gRPC.