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ściowyprotos/buzz.proto
ze ścieżką importu Go o wartościexample.com/project/protos/fizz
tworzy plik wyjściowy w lokalizacjiexample.com/project/protos/fizz/buzz.pb.go
. Jest to domyślny tryb wyjściowy, jeśli nie określono flagipaths
. -
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ściowyprotos/buzz.proto
ze ścieżką importu Go o wartościexample.com/project/protos/fizz
iexample.com/project
podany jako prefiksmodule
tworzy plik wyjściowy o wartościprotos/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ściowyprotos/buzz.proto
daje plik wyjściowy o wartościprotos/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:
- 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.
- 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.