生成Go代码

此页面准确描述了protocol buffer编译器为任何给定协议定义生成的Go代码。 proto2proto3生成代码之间的任何差异都会突出显示 - 请注意,这些差异在本文档中描述的生成代码中,而不是基本API,两个版本中的相同。 在阅读本文档之前,您应该阅读proto2语言指南和/或proto3语言指南。

编译器调用

protocol buffer编译器需要一个插件来生成Go代码。 用它安装

  1. go get github.com/golang/protobuf/protoc-gen-go

这将在$GOBIN安装protoc-gen-go二进制文件。设置$GOBIN环境变量以更改安装位置。它必须是在你$PATHprotocol buffer编译器找到它。使用该--go_out标志调用时,protocol buffer编译器将生成Go输出 。该--go_out标志的参数是您希望编译器在其中写入Go输出的目录。编译器为每个.proto文件输入创建一个源文件。通过将.proto扩展名替换为来创建输出文件的名称.pb.go.proto文件应包含一个go_package选项,用于指定包含所生成代码的Go软件包的完整导入路径。

  1. option go_package ="example.com/foo/bar";

输出文件所在的输出目录的子目录取决于go_package选项和编译器标志:

  • 默认情况下,输出文件放置在以Go软件包的导入路径命名的目录中。例如,protos/foo.proto 具有上述go_package选项的文件将生成名为的文件example.com/foo/bar/foo.pb.go
  • 如果给--go_opt=paths=source_relative标记protoc,则将输出文件放置在与输入文件相同的相对目录中。例如,该文件protos/foo.proto 生成名为的文件protos/foo.pb.go

当你像这样运行proto编译器时:

  1. protoc --proto_path=src --go_out=build/gen --go_opt=paths=source_relative src/foo.proto src/bar/baz.proto

编译器将读取文件src/foo.protosrc/bar/baz.proto。 它产生两个输出文件:build/gen/foo.pb.gobuild/gen/bar/baz.pb.go

如有必要,编译器会自动创建目录build/gen/bar,但不会创建buildbuild/gen; 他们必须已经存在。

.proto文件应包含一个go_package选项,用于指定文件的完整Go导入路径。如果没有go_package 选择,编译器将尝试猜测一个。编译器的未来版本将使该go_package选项成为必需。生成代码的Go软件包名称将是该go_package选项的最后一个路径部分 。

消息

给出一个简单的消息声明:

  1. message Foo {}

protovol buffer编译器生成一个名为的结构Foo。一个*Foo实现proto.Message接口。

proto软件包 提供了对消息进行操作的功能,包括与二进制格式之间的转换。

proto.Message接口定义了一个ProtoReflect方法。此方法返回protoreflect.Message ,提供消息的基于反射的视图。

optimize_for选项不会影响Go代码生成器的输出。

嵌套类型

可以在另一条消息中声明消息。 例如:

  1. message Foo {
  2. message Bar {
  3. }
  4. }

在这种情况下,编译器生成两个结构:FooFoo_Bar

字段

protocol buffer编译器为消息中定义的每个字段生成结构字段。 该字段的确切性质取决于其类型以及它是单个,重复,映射还是单个字段。

请注意,生成的Go字段名称始终使用驼峰式命名,即使.proto文件中的字段名称使用带有下划线的小写字母(应该如此)。 案例转换的工作原理如下:

  • 第一个字母为出口资本化。如果第一个字符是下划线,则将其删除并添加大写字母X.
  • 如果内部下划线后跟小写字母,则删除下划线,并将以下字母大写。

因此,原型字段foo_bar_baz在Go中变为FooBarBaz,而_my_field_name_2变为XMyFieldName_2

单数标量字段(proto2)

对于以下任一字段定义:

  1. optional int32 foo = 1;
  2. required int32 foo = 1;

编译器生成一个结构,其中包含一个名为Foo* int32字段和一个访问器方法GetFoo(),它返回Foo中的int32值或默认值(如果该字段未设置)。 如果未显式设置默认值,则使用该类型的零值(0表示数字,空字符串表示字符串)。

对于其他标量字段类型(包括boolbytesstring),* int32将根据标量值类型表替换为相应的Go类型。

单数标量字段(proto3)

对于此字段定义:

  1. int32 foo = 1;

编译器将生成一个带有名为Fooint32字段和一个访问器方法GetFoo()的结构,该方法返回Foo中的int32值或该字段的零值(如果字段未设置)(数字为0,字符串为空字符串)。

对于其他标量字段类型(包括boolbytesstring),根据标量值类型表将int32替换为相应的Go类型。 proto中的未设置值将表示为该类型的零值(0表示数字,空字符串表示字符串)。

单数消息字段

指定消息类型:

  1. message Bar {}

此消息有Bar字段:

  1. // proto2
  2. message Baz {
  3. optional Bar foo = 1;
  4. // The generated code is the same result if required instead of optional.
  5. }
  6. // proto3
  7. message Baz {
  8. Bar foo = 1;
  9. }

编译器将产生Go的结构体

  1. type Baz struct {
  2. Foo *Bar
  3. }

消息字段可以设置为nil,这意味着该字段未设置,有效清除该字段。 这不等同于将值设置为消息struct的”空”实例。

编译器还生成一个func(m *Baz)GetFoo() *Bar)辅助函数。 这使得可以在没有中间零检查的情况下链接获取调用。

repeated字段

每个重复的字段在Go中的结构中生成一个T字段,其中T是字段的元素类型。 对于带有重复字段的此消息:

  1. message Baz {
  2. repeated Bar foo = 1;
  3. }

编译器将产生Go的结构体:

  1. type Baz struct {
  2. Foo []*Bar
  3. }

同样,对于字段定义,重复字节foo = 1; 编译器将生成一个带有名为Foo[] []字节字段的Go结构。 对于重复的枚举重复MyEnum bar = 2;,编译器生成一个带有名为Bar[] MyEnum字段的结构。

下面的例子展示如何设置该字段

  1. baz := &Baz{
  2. Foo: []*Bar{
  3. {}, // First element.
  4. {}, // Second element.
  5. },
  6. }

要访问该字段,可以这样做

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

Map字段

每个map字段在类型map[TKey]TValue的结构中生成一个字段,其中TKey是字段的键类型,TValue是字段的值类型。 对于带有map字段的此消息:

  1. message Bar {}
  2. message Baz {
  3. map<string, Bar> foo = 1;
  4. }

编译器将生成Go结构体:

  1. type Baz struct {
  2. Foo map[string]*Bar
  3. }

Oneof字段

对于oneof字段,protobuf编译器生成具有接口类型isMessageName_MyField的单个字段。 它还为oneof中的每个奇异字段生成一个结构。 这些都实现了这个isMessageName_MyField接口。

对于带有oneof字段的此消息:

  1. package account;
  2. message Profile {
  3. oneof avatar {
  4. string image_url = 1;
  5. bytes image_data = 2;
  6. }
  7. }

编译器生成结构体:

  1. type Profile struct {
  2. // Types that are valid to be assigned to Avatar:
  3. // *Profile_ImageUrl
  4. // *Profile_ImageData
  5. Avatar isProfile_Avatar `protobuf_oneof:"avatar"`
  6. }
  7. type Profile_ImageUrl struct {
  8. ImageUrl string
  9. }
  10. type Profile_ImageData struct {
  11. ImageData []byte
  12. }

*Profile_ImageUrl*Profile_ImageData都通过提供空的isProfile_Avatar()方法来实现isProfile_Avatar

以下示例显示如何设置字段:

  1. p1 := &account.Profile{
  2. Avatar: &account.Profile_ImageUrl{"http://example.com/image.png"},
  3. }
  4. // imageData is []byte
  5. imageData := getImageData()
  6. p2 := &account.Profile{
  7. Avatar: &account.Profile_ImageData{imageData},
  8. }

要访问该字段,您可以使用值上的类型开关来处理不同的消息类型。

  1. switch x := m.Avatar.(type) {
  2. case *account.Profile_ImageUrl:
  3. // Load profile image based on URL
  4. // using x.ImageUrl
  5. case *account.Profile_ImageData:
  6. // Load profile image based on bytes
  7. // using x.ImageData
  8. case nil:
  9. // The field is not set.
  10. default:
  11. return fmt.Errorf("Profile.Avatar has unexpected type %T", x)
  12. }

编译器还生成get方法func (m *Profile) GetImageUrl()字符串和func (m *Profile) GetImageData) []字节。 每个get函数返回该字段的值,如果未设置则返回零值。

枚举

给出如下枚举:

  1. message SearchRequest {
  2. enum Corpus {
  3. UNIVERSAL = 0;
  4. WEB = 1;
  5. IMAGES = 2;
  6. LOCAL = 3;
  7. NEWS = 4;
  8. PRODUCTS = 5;
  9. VIDEO = 6;
  10. }
  11. Corpus corpus = 1;
  12. ...
  13. }

protocol buffer编译器生成一种类型和一系列具有该类型的常量。

对于消息中的枚举(如上面的那个),类型名称以消息名称开头:

  1. type SearchRequest_Corpus int32

对于包级别的枚举:

  1. enum Foo {
  2. DEFAULT_BAR = 0;
  3. BAR_BELLS = 1;
  4. BAR_B_CUE = 2;
  5. }

他的go类型名称未从proto 枚举名称修改:

  1. type Foo int32

此类型具有String()方法,该方法返回给定值的名称。

Enum()方法使用给定值初始化新分配的内存并返回相应的指针:

  1. func (Foo) Enum() *Foo

protocol buffer编译器为枚举中的每个值生成一个常量。 对于消息中的枚举,常量以封闭消息的名称开头:

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

对于包级别的枚举,常量以枚举名称开头:

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

protobuf编译器还生成从整数值到字符串名称的映射以及从名称到值的映射:

  1. var Foo_name = map[int32]string{
  2. 0: "DEFAULT_BAR",
  3. 1: "BAR_BELLS",
  4. 2: "BAR_B_CUE",
  5. }
  6. var Foo_value = map[string]int32{
  7. "DEFAULT_BAR": 0,
  8. "BAR_BELLS": 1,
  9. "BAR_B_CUE": 2,
  10. }

请注意,.proto语言允许多个枚举符号具有相同的数值。 具有相同数值的符号是同义词。 这些在Go中以完全相同的方式表示,多个名称对应于相同的数值。 反向映射包含数字值的单个条目,该条目首先出现在.proto文件中。

扩展

扩展仅存在于proto2中。 有关proto2扩展的Go生成代码API的文档,请参阅proto包doc

服务

默认情况下,Go代码生成器不会为服务生成输出。 如果您启用gRPC插件(请参阅gRPC Go快速入门指南),则会生成代码以支持gRPC