Nesta página, descrevemos exatamente qual código Go o compilador de buffer de protocolo gera para qualquer definição de protocolo. As diferenças entre o código gerado pelo proto2 e pelo proto3 serão destacadas. Observe que essas diferenças estão no código gerado, conforme descrito neste documento, e não na API base, que são as mesmas nas duas versões. Leia o guia de idiomas do proto2 e/ou o guia do idioma do proto3 antes de ler este documento.
Invocação do compilador
O compilador de buffer de protocolo requer um plug-in para gerar o código Go. Instale-o usando o Go 1.16 ou versão mais recente executando:
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
Isso vai instalar um binário protoc-gen-go
no $GOBIN
.
Defina a variável de ambiente $GOBIN
para mudar o local de instalação.
Ele precisa estar no $PATH
para que o compilador de buffer de protocolo o encontre.
O compilador de buffer de protocolo produz a saída do Go quando invocado com a sinalização go_out
.
O argumento para a sinalização go_out
é o diretório em que você quer que o compilador grave
a saída do Go. O compilador cria um único arquivo de origem para cada entrada de arquivo .proto
.
O nome do arquivo de saída é criado substituindo a extensão .proto
por .pb.go
.
Onde no diretório de saída o arquivo .pb.go
gerado é colocado
depende das sinalizações do compilador. Há vários modos de saída:
-
Se a sinalização
paths=import
for especificada, o arquivo de saída será colocado em um diretório nomeado após o caminho de importação do pacote Go. Por exemplo, um arquivo de entradaprotos/buzz.proto
com um caminho de importação do Go deexample.com/project/protos/fizz
resulta em um arquivo de saída emexample.com/project/protos/fizz/buzz.pb.go
. Esse é o modo de saída padrão se uma sinalizaçãopaths
não for especificada. -
Se a sinalização
module=$PREFIX
for especificada, o arquivo de saída será colocado em um diretório nomeado após o caminho de importação do pacote Go, mas com o prefixo de diretório especificado removido do nome de arquivo de saída. Por exemplo, um arquivo de entradaprotos/buzz.proto
com um caminho de importação do Go deexample.com/project/protos/fizz
eexample.com/project
especificado como o prefixomodule
resulta em um arquivo de saída emprotos/fizz/buzz.pb.go
. A geração de qualquer pacote Go fora do caminho do módulo resulta em erro. Esse modo é útil para gerar arquivos gerados diretamente em um módulo Go. -
Se a sinalização
paths=source_relative
for especificada, o arquivo de saída será colocado no mesmo diretório relativo que o arquivo de entrada. Por exemplo, um arquivo de entradaprotos/buzz.proto
resulta em um arquivo de saída emprotos/buzz.pb.go
.
As sinalizações específicas para protoc-gen-go
são transmitidas ao transmitir uma sinalização go_opt
ao invocar protoc
. Várias sinalizações go_opt
podem ser transmitidas.
Por exemplo, ao executar:
protoc --proto_path=src --go_out=out --go_opt=paths=source_relative foo.proto bar/baz.proto
O compilador lerá
os arquivos de entrada foo.proto
e bar/baz.proto
no diretório src
e
gravará os arquivos de saída foo.pb.go
e bar/baz.pb.go
no diretório out
.
O compilador cria automaticamente subdiretórios de saída aninhados, se necessário,
mas não cria o diretório de saída.
Entrega de pacotes
Para gerar o código Go, o caminho de importação do pacote Go precisa ser fornecido
para cada arquivo .proto
, incluindo aqueles dependentes temporariamente
dos arquivos .proto
que estão sendo gerados. Há duas maneiras de especificar o caminho de importação do Go:
- declarando-o no arquivo
.proto
ou - ao declará-lo na linha de comando ao invocar
protoc
.
Recomendamos a declaração no arquivo .proto
para que os pacotes Go para
arquivos .proto
possam ser identificados de forma centralizada com os arquivos .proto
,
além de simplificar o conjunto de sinalizações transmitidas ao invocar protoc
.
Se o caminho de importação do Go para um determinado arquivo .proto
for fornecido pelo próprio arquivo .proto
e na linha de comando, o último terá precedência sobre o anterior.
O caminho de importação do Go é especificado localmente em um arquivo .proto
, declarando uma opção go_package
com o caminho de importação completo do pacote do Go. Exemplo de uso:
option go_package = "example.com/project/protos/fizz";
O caminho de importação Go pode ser especificado na linha de comando ao invocar o compilador,
transmitindo uma ou mais sinalizações M${PROTO_FILE}=${GO_IMPORT_PATH}
. Exemplo de uso:
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
Como o mapeamento de todos os arquivos .proto
para os caminhos de importação do Go pode ser muito grande,
esse modo de especificar os caminhos de importação do Go geralmente é realizado por alguma ferramenta de build
(por exemplo, Bazel) que tenha controle sobre toda a árvore de dependências.
Se houver entradas duplicadas para um determinado arquivo .proto
,
a última especificada terá precedência.
Para a opção go_package
e a sinalização M
,
o valor pode incluir um nome de pacote explícito separado do caminho de importação por um ponto e vírgula.
Por exemplo: "example.com/protos/foo;package_name"
.
Esse uso não é recomendado, já que o nome do pacote
será derivado por padrão pelo caminho de importação de maneira razoável.
O caminho de importação é usado para determinar quais instruções de importação precisam ser geradas
quando um arquivo .proto
importa outro arquivo .proto
.
Por exemplo, se a.proto
importar b.proto
,
o arquivo a.pb.go
gerado precisará importar o pacote Go que contém
o arquivo b.pb.go
gerado (a menos que os dois arquivos estejam no mesmo pacote).
O caminho de importação também é usado para construir nomes de arquivos de saída.
Consulte a seção "Invocação do compilador" acima para ver detalhes.
Não há correlação entre o caminho de importação do Go e o
especificador package
no arquivo .proto
. O último só é relevante para o namespace protobuf,
enquanto o primeiro é relevante apenas para o namespace Go.
Além disso, não há correlação entre o caminho de importação do Go e o caminho de importação do .proto
.
Mensagens
Se a declaração for simples:
message Foo {}
O compilador de buffer de protocolo gera uma estrutura chamada Foo
. Uma
*Foo
implementa a
interface
proto.Message
.
O
pacote proto
fornece funções que operam em mensagens, incluindo a conversão de e para o formato binário.
A interface proto.Message
define um método ProtoReflect
.
Esse método retorna um
protoreflect.Message
,
que fornece uma visualização da mensagem com base em reflexão.
A opção optimize_for
não afeta a saída do gerador de código Go.
Tipos aninhados
Uma mensagem pode ser declarada dentro de outra. Por exemplo:
message Foo { message Bar { } }
Nesse caso, o compilador gera duas structs: Foo
e Foo_Bar
.
Campos
O compilador de buffer de protocolo gera um campo struct para cada campo definido em uma mensagem. A natureza exata desse campo depende do tipo dele e de ser um campo único, repetido, de mapa ou único.
Os nomes dos campos Go gerados sempre usam letras minúsculas no nome do campo, mesmo que
o nome do campo no arquivo .proto
use letras minúsculas com sublinhados,
o que é necessário. A conversão de caso
funciona da seguinte maneira:
- A primeira letra é maiúscula para a exportação. Se o primeiro caractere for um sublinhado, ele será removido e um X maiúsculo vai ser precedido.
- Se um sublinhado interno for seguido por uma letra minúscula, o sublinhado será removido e a letra a seguir será maiúscula.
Assim, o campo proto foo_bar_baz
se torna
FooBarBaz
em Go, e _my_field_name_2
se torna
XMyFieldName_2
.
Campos escalares individuais (proto2)
Para qualquer uma dessas definições de campo:
optional int32 foo = 1; required int32 foo = 1;
O compilador gera um struct com um campo *int32
chamado
Foo
e um método de acessador GetFoo()
que retorna o
valor int32
em Foo
ou o valor padrão se o campo
não estiver definido. Se o padrão não for definido explicitamente, o
valor zero desse
tipo será usado (0
para números, a string vazia para
strings).
Para outros tipos de campo escalar (incluindo bool
, bytes
e string
), *int32
é substituído pelo tipo Go correspondente, de acordo com a tabela de tipos de valores escalares.
Campos de escalonamento escalar (proto3)
Para esta definição de campo:
int32 foo = 1;O compilador gerará uma struct com um campo
int32
chamado
Foo
e um método de acessador GetFoo()
que retornará o
valor int32
em Foo
ou o
valor zero desse
tipo se o campo não estiver definido (0
para números, a string vazia para
strings).
Para outros tipos de campo escalar (incluindo bool
, bytes
e string
), int32
é substituído pelo tipo Go correspondente, de acordo com a tabela de tipos de valores escalares.
Valores não definidos no proto serão representados como o
valor zero desse
tipo (0
para números, a string vazia para strings).
Campos de mensagens individuais
Considerando o tipo de mensagem:
message Bar {}Para uma mensagem com um campo
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; }O compilador gerará uma estrutura Go
type Baz struct { Foo *Bar }
Os campos de mensagem podem ser definidos como nil
, o que significa que o campo não está definido, limpando o campo. Isso não
é equivalente a definir o valor como uma instância "vazia" do struct da mensagem.
O compilador também gera uma função auxiliar func (m *Baz) GetFoo() *Bar
. Essa função retornará um nil
*Bar
se m
for nulo ou foo
não for definido. Isso permite
encadear chamadas de recebimento sem verificações nil
intermediárias.
Campos repetidos
Cada campo repetido gera uma fração do campo T
no struct
em Go, em que T
é o tipo de elemento do campo. Para esta mensagem com
um campo repetido:
message Baz { repeated Bar foo = 1; }
O compilador gera o struct Go:
type Baz struct { Foo []*Bar }
Da mesma forma, para a definição de campo repeated bytes foo = 1;
, o
compilador gerará uma estrutura Go com um campo [][]byte
chamado
Foo
. Para uma enumeração
repeated MyEnum bar = 2;
repetida, o compilador gera uma estrutura
com um campo []MyEnum
chamado Bar
.
O exemplo a seguir mostra como definir o campo:
baz := &Baz{ Foo: []*Bar{ {}, // First element. {}, // Second element. }, }
Para acessar o campo, faça o seguinte:
foo := baz.GetFoo() // foo type is []*Bar. b1 := foo[0] // b1 type is *Bar, the first element in foo.
Campos do mapa
Cada campo de mapa gera um campo no struct do tipo map[TKey]TValue
, em que TKey
é o tipo de chave do campo e TValue
é o tipo de valor do campo. Para esta mensagem com um campo de mapa:
message Bar {} message Baz { map<string, Bar> foo = 1; }
O compilador gera o struct Go:
type Baz struct { Foo map[string]*Bar }
Oneof Fields
Para um campo oneof, o compilador protobuf gera um único campo com um
tipo de interface isMessageName_MyField
. Ele também gera uma estrutura
para cada um dos campos individuais em um. Todos eles implementam esta interface isMessageName_MyField
.
Para esta mensagem com um campo oneof:
package account; message Profile { oneof avatar { string image_url = 1; bytes image_data = 2; } }
o compilador gera as estruturas:
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 }
Tanto *Profile_ImageUrl
quanto *Profile_ImageData
implementam isProfile_Avatar
, fornecendo um método
isProfile_Avatar()
vazio.
O exemplo a seguir mostra como definir o campo:
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}, }
Para acessar o campo, use uma chave de tipo no valor para processar os diferentes tipos de mensagens.
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) }
O compilador também gera os métodos get
func (m *Profile) GetImageUrl() string
e
func (m *Profile) GetImageData() []byte
. Cada função "get" retornará o valor desse campo ou o valor zero se ele não estiver definido.
Enumerações
Dada uma enumeração como:
message SearchRequest { enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 1; ... }
O compilador de buffer de protocolo gera um tipo e uma série de constantes com esse tipo.
Para enumerações dentro de uma mensagem (como a mostrada acima), o nome do tipo começa com o nome da mensagem:
type SearchRequest_Corpus int32
Para uma enumeração no nível do pacote:
enum Foo { DEFAULT_BAR = 0; BAR_BELLS = 1; BAR_B_CUE = 2; }
O nome do tipo Go não é modificado no nome da enumeração proto:
type Foo int32
Esse tipo tem um método String()
que retorna o nome de um
determinado valor.
O método Enum()
inicializa a memória alocada com um valor específico e
retorna o ponteiro correspondente:
func (Foo) Enum() *Foo
Se você usar a sintaxe proto3 para sua definição de .proto
, o método "Enum()" não será gerado.
O compilador de buffer de protocolo gera uma constante para cada valor na enumeração. Para enumerações dentro de uma mensagem, as constantes começam com o nome da mensagem de inclusão:
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 )
Para uma enumeração no nível do pacote, as constantes começam com o nome da enumeração:
const ( Foo_DEFAULT_BAR Foo = 0 Foo_BAR_BELLS Foo = 1 Foo_BAR_B_CUE Foo = 2 )
O compilador protobuf também gera um mapa dos valores inteiros para os nomes de strings e um mapa dos nomes para os valores:
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, }
Observe que o idioma .proto
permite que vários símbolos de enumeração
tenham o mesmo valor numérico. Símbolos com o mesmo valor numérico são sinônimos.
Eles são representados em Go da mesma forma, com vários nomes correspondentes ao mesmo valor numérico. O mapeamento reverso contém uma única
entrada para o valor numérico para o nome que aparece primeiro no arquivo .proto.
Extensões (proto2)
Com uma definição de extensão:
extend Foo { optional int32 bar = 123; }
O compilador de buffer de protocolo gerará um
valor protoreflect.ExtensionType
chamado E_Bar
. Esse valor pode ser usado com as funções
proto.GetExtension
,
proto.SetExtension
,
proto.HasExtension
e
proto.ClearExtension
para acessar uma extensão em uma mensagem. As funções GetExtension
e
SetExtension
aceitam e retornam, respectivamente, um valor interface{}
contendo o tipo de valor de extensão.
Em campos de extensão escalar singulares, o tipo de valor de extensão é o tipo Go correspondente da tabela de tipos de valores escalares.
Para campos de extensão de mensagem incorporados singulares, o tipo de valor de extensão é *M
, em que M
é o tipo de mensagem de campo.
Para campos de extensão repetidos, o tipo de valor de extensão é uma fração do tipo singular.
Por exemplo, considerando a seguinte definição:
extend Foo { optional int32 singular_int32 = 1; repeated bytes repeated_string = 2; optional Bar singular_message = 3; }
Os valores de extensão podem ser acessados como:
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)
As extensões podem ser declaradas aninhadas em outro tipo. Por exemplo, um padrão comum é fazer isto:
message Baz { extend Foo { optional Baz foo_ext = 124; } }
Nesse caso, o valor ExtensionType
é chamado de E_Baz_Foo
.
Serviços
Por padrão, o gerador de código Go não gera resultados para serviços. Se você ativar o plug-in gRPC (consulte o Guia de início rápido do gRPC Go), o código será gerado para oferecer suporte ao gRPC.