OBSERVAÇÃO:este site foi descontinuado. O site será desativado após 31 de janeiro de 2023, e o tráfego será redirecionado para o novo site em https://protobuf.dev. Enquanto isso, as atualizações serão feitas apenas para protobuf.dev.

Código gerado pelo Go

Mantenha tudo organizado com as coleções Salve e categorize o conteúdo com base nas suas preferências.

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 entrada protos/buzz.proto com um caminho de importação do Go de example.com/project/protos/fizz resulta em um arquivo de saída em example.com/project/protos/fizz/buzz.pb.go. Esse é o modo de saída padrão se uma sinalização paths 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 entrada protos/buzz.proto com um caminho de importação do Go de example.com/project/protos/fizz e example.com/project especificado como o prefixo module resulta em um arquivo de saída em protos/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 entrada protos/buzz.proto resulta em um arquivo de saída em protos/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:

  1. 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.
  2. 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.