UWAGA: witryna została wycofana. Po 31 stycznia 2023 roku witryna zostanie wyłączona, a ruch będzie kierowany do nowej witryny na https://protobuf.dev. Do tego czasu aktualizacje będą dotyczyć tylko protobuf.dev.

Wygeneruj wygenerowany kod

Zadbaj o dobrą organizację dzięki kolekcji Zapisuj i kategoryzuj treści zgodnie ze swoimi preferencjami.

Ta strona zawiera dokładny kod wygenerowany przez kompilator bufora protokołu w przypadku danej definicji protokołu. Zwróć uwagę na różnice między kodem wygenerowanym przez proto2 a kodem proto3 – pamiętaj, że chodzi o wygenerowany kod w sposób opisany w tym dokumencie, a nie o podstawowy interfejs API, który jest taki sam w obu wersjach. Zanim przeczytasz ten dokument, przeczytaj przewodnik po języku proto2 lub przewodnik po języku proto3.

Wywołanie kompilatora

Kompilator bufora protokołu wymaga wtyczki do generowania kodu Go. Zainstaluj ją w systemie Go 1.16 lub nowszym, uruchamiając polecenie:

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

Spowoduje to zainstalowanie pliku binarnego protoc-gen-go w lokalizacji $GOBIN. Ustaw zmienną środowiskową $GOBIN, aby zmienić lokalizację instalacji. Musi go znaleźć w $PATH, aby kompilator bufora protokołu mógł go znaleźć.

Kompilator bufora protokołu generuje wynik Go po wywołaniu flagą go_out. Argument flagi go_out to katalog, w którym kompilator ma zapisać dane wyjściowe w języku Go. Kompilator tworzy jeden plik źródłowy na każdy przesłany plik .proto. Nazwa pliku wyjściowego powstaje przez zastąpienie rozszerzenia .proto tekstem .pb.go.

Gdzie w katalogu wyjściowym znajduje się wygenerowany plik .pb.go, zależy od flagów kompilacji. Dostępnych jest kilka trybów wyjściowych:

  • Jeśli flaga paths=import jest określona, plik wyjściowy jest umieszczany w katalogu o nazwie odpowiadającej ścieżce importowania pakietu Go. Na przykład plik wejściowy protos/buzz.proto ze ścieżką importu Go o wartości example.com/project/protos/fizz tworzy plik wyjściowy w lokalizacji example.com/project/protos/fizz/buzz.pb.go. Jest to domyślny tryb wyjściowy, jeśli nie określono flagi paths.
  • Jeśli flaga module=$PREFIX jest określona, plik wyjściowy jest umieszczany w katalogu o nazwie odpowiadającej ścieżce importowania pakietu Go, ale z określonego prefiksu katalogu usunięto z nazwy pliku wyjściowego. Na przykład plik wejściowy protos/buzz.proto ze ścieżką importu Go o wartości example.com/project/protos/fizz i example.com/project podany jako prefiks module tworzy plik wyjściowy o wartości protos/fizz/buzz.pb.go. Wygenerowanie dowolnych pakietów Go poza ścieżką modułu powoduje błąd. Ten tryb przydaje się do generowania wygenerowanych plików bezpośrednio w module Go.
  • Jeśli flaga paths=source_relative jest określona, plik wyjściowy jest umieszczany w tym samym katalogu względnym co plik wejściowy. Na przykład plik wejściowy protos/buzz.proto daje plik wyjściowy o wartości protos/buzz.pb.go.

Flagi charakterystyczne dla protoc-gen-go są przekazywane, przesyłając flagę go_opt podczas wywoływania protoc. Można przekazywać wiele flag go_opt. Na przykład podczas wyświetlania:

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

Kompilator odczyta pliki wejściowe foo.proto i bar/baz.proto z katalogu src oraz zapisze pliki wyjściowe foo.pb.go i bar/baz.pb.go w katalogu out. Kompilator automatycznie tworzy podkatalogi wyjściowe zagnieżdżone, jeśli to konieczne, ale nie tworzy samego katalogu wyjściowego.

Pakiety

Aby wygenerować kod Go, dla każdego pliku .proto należy podać ścieżkę importowania pakietu w języku Go (w tym także w zależności od generowanych plików .proto). Ścieżka importu Go można określić na 2 sposoby:

  • deklarując go w pliku .proto lub
  • deklarując go w wierszu poleceń podczas wywoływania protoc.

Zalecamy zadeklarowanie go w pliku .proto, by pakiety Go dla plików .proto mogły być centralnie identyfikowane za pomocą plików .proto i uprościć zestaw flag przekazywanych podczas wywoływania protoc. Jeśli ścieżka importowania Go w przypadku danego pliku .proto jest podana zarówno w pliku .proto, jak i w wierszu poleceń, pierwszeństwo ma ta druga.

Ścieżka importu w języku Go jest określona lokalnie w pliku .proto przez zadeklarowanie opcji go_package z pełną ścieżką importu pakietu Go. Przykład użycia:

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

Ścieżka importowania Go można określić w wierszu poleceń podczas wywoływania kompilatora przez przesłanie co najmniej 1 flagi M${PROTO_FILE}=${GO_IMPORT_PATH}. Przykład użycia:

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

Mapowanie wszystkich plików .proto na ścieżki importu w Go może być dość duże, dlatego ten tryb określania ścieżek importu Go jest zwykle obsługiwany przez niektóre narzędzia do tworzenia (np. Bazel), która ma kontrolę nad całym drzewem zależności. Jeśli w danym pliku .proto są zduplikowane wpisy, pierwszeństwo ma ten ostatni.

W przypadku opcji go_package i flagi M wartość może zawierać jawną nazwę pakietu oddzieloną średnikiem. na przykład: "example.com/protos/foo;package_name". Nie jest to zalecane, ponieważ nazwa pakietu jest domyślnie pobierana ze ścieżki importu w rozsądny sposób.

Ścieżka importu służy do określania, które instrukcje importu należy wygenerować, gdy jeden plik .proto importuje inny plik .proto. Jeśli na przykład a.proto importuje b.proto, wygenerowany plik a.pb.go musi zaimportować pakiet Go zawierający plik b.pb.go (chyba że oba pliki znajdują się w tym samym pakiecie). Ścieżka importu służy też do tworzenia nazw plików wyjściowych. Szczegółowe informacje znajdziesz powyżej w sekcji „Wywołanie kompilatora”.

Nie ma korelacji między ścieżką importu w języku Go a specyfikacją package w pliku .proto. Ta druga opcja dotyczy tylko przestrzeni nazw protobuf, a druga dotyczy tylko przestrzeni nazw w języku Go. Nie ma też korelacji między ścieżką importu w Go a ścieżką importowania .proto.

Wiadomości

Oto prosta deklaracja wiadomości:

message Foo {}

Kompilator bufora protokołu generuje strukturę o nazwie Foo. *Foo wykorzystuje interfejs proto.Message.

Pakiet proto udostępnia funkcje działające na wiadomościach, w tym konwersje na formaty binarne i odwrotnie.

Interfejs proto.Message określa metodę ProtoReflect. Ta metoda zwraca protoreflect.Message z komunikatem opartym na odbiciu.

Opcja optimize_for nie wpływa na dane wyjściowe generatora kodów Go.

Typy zagnieżdżone

Daną wiadomość można zadeklarować w innej wiadomości. Na przykład:

message Foo {
  message Bar {
  }
}

W tym przypadku kompilator generuje 2 elementy: Foo i Foo_Bar.

Pola

Kompilator bufora protokołu generuje pole struktury dla każdego pola zdefiniowanego w wiadomości. Dokładny charakter tego pola zależy od jego typu i od tego, czy jest to pole liczby pojedynczej, powtórzonej, mapy czy jednego.

Nazwy utworzonych pól Go zawsze używają nazw wielbłądów, nawet jeśli nazwa pola w pliku .proto zawiera małe litery z podkreśleniami (jak powinna). Konwersja według zgłoszenia działa w ten sposób:

  1. Pierwsza litera jest wyeksportowana wielką literą. Jeśli pierwszy znak jest podkreśleniem, zostanie usunięty, a na jego początku pojawi się litera X.
  2. Jeśli wewnątrz słowa znajduje się podkreślenie, małe litery są podkreślane, a następna litera wielką literą.

W związku z tym pole proto foo_bar_baz zmieni się w FooBarBaz w Go, a _my_field_name_2 zmieni się na XMyFieldName_2.

Pola skalarne w liczbie pojedynczej (proto2)

W przypadku dowolnej z tych definicji pól:

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

Kompilator generuje konstrukcję z polem *int32 o nazwie Foo oraz metodą metody dostępu GetFoo(), która zwraca wartość int32 w Foo lub wartość domyślną, jeśli pole jest nieskonfigurowane. Jeśli wartość domyślna nie jest określona, używana jest zero wartości danego typu (0 w przypadku liczb, pusty ciąg znaków w przypadku ciągów tekstowych).

W przypadku innych typów pól skalarnych (w tym bool, bytes i string) *int32 zostaje zastąpione odpowiednim typem Go zgodnie z tabelą typów wartości skalarnych.

Pola skalarne w liczbie pojedynczej (proto3)

Dla tej definicji pola:

int32 foo = 1;
Kompilator wygeneruje strukturę z polem int32 o nazwie Foo oraz metodą metody dostępu GetFoo(), która zwraca wartość int32 w Foo lub zerową wartość tego typu, jeśli pole jest nieskonfigurowane (0 w przypadku liczb, pusty ciąg znaków w przypadku ciągów).

W przypadku innych typów pól skalarnych (w tym bool, bytes i string) int32 zostaje zastąpione odpowiednim typem Go zgodnie z tabelą typów wartości skalarnych. Wartości nieskonfigurowane w protocie będą reprezentowane jako zerowe wartości tego typu (0 w przypadku liczb, pusty ciąg znaków w przypadku ciągów tekstowych).

Pojedyncze pola wiadomości

Biorąc pod uwagę typ wiadomości:

message Bar {}
W przypadku wiadomości z polem 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;
}
Kompilator wygeneruje strukturę znaczników Go
type Baz struct {
	Foo *Bar
}

Pole komunikatów może mieć wartość nil, co oznacza, że pole jest nieskonfigurowane, co powoduje dokładne wyczyszczenie pola. Nie jest to równoważne ustawieniu wartości na „pustą” instancję struktury wiadomości.

Kompilator generuje też funkcję pomocniczą func (m *Baz) GetFoo() *Bar. Ta funkcja zwraca nil *Bar, jeśli m to nil, a foo jest nieskonfigurowana. Pozwala to łączyć łańcuchy połączeń bez weryfikacji nil.

Pola powtarzane

Każde pole powtarzane generuje wycinek pola T w strukturze Go, gdzie T to typ elementu pola. Dla tej wiadomości z powtarzającym się polem:

message Baz {
  repeated Bar foo = 1;
}

Kompilator generuje strukturę Go:

type Baz struct {
	Foo  []*Bar
}

I podobnie, w przypadku definicji pola repeated bytes foo = 1; kompilator wygeneruje konstrukcję Go z polem [][]byte o nazwie Foo. Na potrzeby powtarzającego się wyliczenia repeated MyEnum bar = 2; kompilator generuje strukturę z polem []MyEnum o nazwie Bar.

Poniższy przykład pokazuje, jak ustawić pole:

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

Aby uzyskać dostęp do pola, możesz wykonać te czynności:

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

Pola mapy

Każde pole mapy generuje pole w strukturze typu map[TKey]TValue, gdzie TKey to typ klucza pola, a TValue to typ wartości pola. W przypadku tego komunikatu z polem mapy:

message Bar {}

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

Kompilator generuje strukturę Go:

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

Pola uniwersalne

Kompilator protobuf generuje jedno pole o typie interfejsu użytkownika: isMessageName_MyField. Generuje też strukturę każdego pojedynczego pola w tym polu. Wszystkie te funkcje mają interfejs isMessageName_MyField.

W przypadku wiadomości z polem oneof:

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

Kompilator generuje struktury:

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
}

Zarówno *Profile_ImageUrl, jak i *Profile_ImageData implementują isProfile_Avatar, podając pustą metodę isProfile_Avatar().

Poniższy przykład pokazuje, jak ustawić pole:

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},
}

Aby uzyskać dostęp do tego pola, możesz użyć przełącznika typu wartości do obsługi różnych typów wiadomości.

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)
}

Kompilator generuje też metody func (m *Profile) GetImageUrl() string i func (m *Profile) GetImageData() []byte. Każda funkcja get zwraca wartość tego pola lub wartość zerową, jeśli nie jest ustawiona.

Wyliczenia

Na podstawie tego wyliczenia:

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

Kompilator bufora protokołu generuje typ i serię stałych z tym typem.

W przypadku wyliczenia w wiadomości (jak powyżej) nazwa typu zaczyna się od nazwy wiadomości:

type SearchRequest_Corpus int32

W przypadku wyliczenia na poziomie pakietu:

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

Nazwa typu go nie jest modyfikowana na podstawie nazwy proto en:

type Foo int32

Ten typ ma metodę String(), która zwraca nazwę określonej wartości.

Metoda Enum() inicjuje nowo przydzieloną pamięć z określoną wartością i zwraca odpowiedni wskaźnik:

func (Foo) Enum() *Foo

Jeśli w definicji .proto używasz składni proto3, metoda „Enum()” nie jest generowana.

Kompilator bufora protokołu generuje stałą dla każdej wartości wyliczenia. W przypadku wyliczeń w wiadomości stałe stałą zaczynają się od nazwy wiadomości zamykającej:

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
)

W przypadku wyliczenia na poziomie pakietu stałe wartości zaczynają się od nazwy wyliczenia:

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

Kompilator protobuf generuje też mapę z wartości całkowitych do nazw ciągów oraz mapę z nazw na wartości:

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,
}

Pamiętaj, że język .proto umożliwia użycie wielu symboli enum w tej samej wartości liczbowej. Symbole o tej samej wartości liczbowej to synonimy. Są one przedstawione w dokładnie tak samo, a wiele nazw odpowiada tej samej wartości liczbowej. Mapowanie wsteczne zawiera jeden wpis dla wartości liczbowej, który pojawia się jako pierwszy w pliku .proto.

Rozszerzenia (proto2)

Biorąc pod uwagę definicję rozszerzenia:

extend Foo {
  optional int32 bar = 123;
}

Kompilator bufora protokołu generuje wartość protoreflect.ExtensionType o nazwie E_Bar. Tej wartości możesz używać w funkcjach proto.GetExtension, proto.SetExtension, proto.HasExtension i proto.ClearExtension, aby uzyskać dostęp do rozszerzenia w wiadomości. Funkcje GetExtension i SetExtension obsługują odpowiednio wartość interface{} zawierającą typ wartości rozszerzenia.

W przypadku pojedynczych pól skalarnych typ wartości rozszerzenia to odpowiedni typ Go z tabeli typów wartości skalarnych.

W przypadku pojedynczych pól osadzonego rozszerzenia wiadomości typ wartości rozszerzenia to *M, gdzie M to typ pola pola.

W przypadku pól powtórzonych typ wartości rozszerzenia jest wycinkiem liczby pojedynczej.

Na przykład w związku z tą definicją:

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

Wartości rozszerzeń są dostępne po kliknięciu:

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)

Rozszerzenia mogą być deklarowane w obrębie innego typu. Typowym wzorcem jest na przykład:

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

W tym przypadku ExtensionType ma nazwę E_Baz_Foo.

Usługi

Generator kodu Go nie generuje domyślnie danych wyjściowych dla usług. Jeśli włączysz wtyczkę gRPC (zobacz krótki przewodnik po gRPC Go), zostanie wygenerowany kod pozwalający na obsługę gRPC.