声明:版权所有,谢绝转载。

    在 go 中使用 google protobuf,有两个可选用的包: goprotobuf(go 官方出品)和gogoprotobuf(gogo 组织出品 )。

    gogoprotobuf 能够完全兼容 google protobuf。而且经过我一些测试,它生成大代码质量确实要比 goprotobuf 高一些,主要是它在 goprotobuf 之上 extend 了一些 option。这些 option 也是有级别区分的,有的 option 只能修饰 field,有的可以修饰 enum,有的可以修饰 message,有的是修饰 package(即对整个文件都有效)。下面一一说明其一些选项的意义。

    另外,本文的所有 proto 示例都是 proto v3 格式。

    1 gogoproto.goproto_enum_prefix

    如果选项为 false,则生成的代码中不加 “E_”。

    1. pb code:
    2. enum E {
    3. // option (gogoproto.goproto_enum_prefix) = false;
    4. A = 0;
    5. B = 2;
    6. }
    7. go code:
    8. const (
    9. // option (gogoproto.goproto_enum_prefix) = false;
    10. E_A E = 0
    11. E_B E = 2
    12. )
    13. or
    14. pb code:
    15. enum E {
    16. // option (gogoproto.goproto_enum_prefix) = false;
    17. A = 0;
    18. B = 2;
    19. }
    20. go code:
    21. const (
    22. A E = 0
    23. B E = 2
    24. )

    2 gogoproto.goproto_getters

    如果选项为 false,则不会为 message 的每个 field 生成一个 Get 函数。

    1. pb code:
    2. message test {
    3. E e = 1;
    4. }
    5. go code:
    6. type Test struct {
    7. E *E `protobuf:"varint,1,opt,name=e,enum=test.E" json:"e,omitempty"`
    8. XXX_unrecognized []byte `json:"-"`
    9. }
    10. func (m *Test) GetE() E {
    11. if m != nil && m.E != nil {
    12. return *m.E
    13. }
    14. return A
    15. }
    16. or
    17. pb code:
    18. message test {
    19. option (gogoproto.goproto_getters) = false;
    20. E e = 1;
    21. }
    22. go code:
    23. type Test struct {
    24. E *E `protobuf:"varint,1,opt,name=e,enum=test.E" json:"e,omitempty"`
    25. XXX_unrecognized []byte `json:"-"`
    26. }

    3 gogoproto.face

    当这个选项为 true 的时候,会为 message 生成相应的 interface。

    1. message test {
    2. option (gogoproto.goproto_getters) = false;
    3. option (gogoproto.face) = true;
    4. string msg = 1;
    5. }
    6. type Test struct {
    7. Msg *string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"`
    8. XXX_unrecognized []byte `json:"-"`
    9. }
    10. func (m *Test) Reset() { *m = Test{} }
    11. func (m *Test) String() string { return proto.CompactTextString(m) }
    12. func (*Test) ProtoMessage() {}
    13. func init() {
    14. proto.RegisterEnum("test.E", E_name, E_value)
    15. }
    16. type TestFace interface {
    17. Proto() github_com_gogo_protobuf_proto.Message
    18. GetMsg() *string
    19. }
    20. func (this *Test) Proto() github_com_gogo_protobuf_proto.Message {
    21. return this
    22. }
    23. func (this *Test) TestProto() github_com_gogo_protobuf_proto.Message {
    24. return NewTestFromFace(this)
    25. }
    26. func (this *Test) GetMsg() *string {
    27. return this.Msg
    28. }
    29. func NewTestFromFace(that TestFace) *Test {
    30. this := &Test{}
    31. this.Msg = that.GetMsg()
    32. return this
    33. }

    这个选项要求 goproto_getters 选项为 false,即只生成 interface 相应的 method。否则,你会收到如下 error:

    1. panic: face requires getters to be disabled please use gogoproto.getters or gogoproto.getters_all and set it to false
    2. goroutine 1 [running]:
    3. github.com/gogo/protobuf/plugin/face.(*plugin).Generate(0x2086c00a0, 0x20870b780)
    4. /Users/Alex/bin/go/src/github.com/gogo/protobuf/plugin/face/face.go:164 +0x37d
    5. github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).runPlugins(0x2087001c0, 0x20870b780)
    6. /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:1008 +0x91
    7. github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).generate(0x2087001c0, 0x20870b780)
    8. /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:1047 +0x3e1
    9. github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).GenerateAllFiles(0x2087001c0)
    10. /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:994 +0x249
    11. main.main()
    12. /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/main.go:96 +0x31d

    4 gogoproto.nullable

    有没有注意到上面的示例中 Test 的成员 msg 类型为 string*,当你要向它赋值的时候,可能要写出如下代码:

    1. var test Test
    2. test.Msg = new(string)
    3. *test.Msg = "test.msg"
    4. or
    5. test := Test{Msg:proto.String("hello")}

    是不是感觉很麻烦?而且生成一堆临时对象,不利于 gc。此时如果 nullable 选项为 false,就不用这么麻烦了

    1. pb code:
    2. message test {
    3. string msg = 1 [(gogoproto.nullable) = false];
    4. }
    5. go code:
    6. type Test struct {
    7. Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
    8. XXX_unrecognized []byte `json:"-"`
    9. }

    严格地说,nullable 这个 option 是违背 protobuf 的初衷的。使用了它之后,message 序列化的时候,gogo 会为 message 的每个 field 设置一个值,而 google protobuf 则是要求如果一个 option 的 field 没有被赋值,则序列化的时候不会把这个成员序列化进最终结果的。gogo 官方的详尽解释是:

    The protocol buffer specification states, somewhere, that you should be able to tell whether a field is set or unset. With the option nullable=false this feature is lost, since your non-nullable fields will always be set. It can be seen as a layer on top of protocol buffers, where before and after marshalling all non-nullable fields are set and they cannot be unset.

    ref: https://godoc.org/code.google.com/p/gogoprotobuf/gogoproto

    注意: bytes 成员不要使用这个 option,否则会收到编译警告 “WARNING: field Message.Data is a non-nullable bytes type, nullable=false has no effect”

    5 gogoproto.customname

    在生成的代码中修改成员的名称,这个选项在这种情况下非常有用:field 的名称与 message 的 method 的名称一样。

    1. pb code:
    2. message test {
    3. option (gogoproto.goproto_getters) = false;
    4. string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
    5. }
    6. go code:
    7. type Test struct {
    8. MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
    9. XXX_unrecognized []byte `json:"-"`
    10. }

    类似的选项还有 gogoproto.customtype,不再赘述。

    6 gogoproto.goproto_stringer

    此选项不设置的时候,其为 true。当这个选项为 false 的时候,gogo 不再为 message 对一个的 struct 生成 String() 方法。这个选项建议不要设置为 false。

    1. pb code:
    2. message test {
    3. option (gogoproto.goproto_stringer) = true;
    4. string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
    5. }
    6. go code:
    7. type Test struct {
    8. MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
    9. XXX_unrecognized []byte `json:"-"`
    10. }
    11. func (m *Test) Reset() { *m = Test{} }
    12. func (m *Test) String() string { return proto.CompactTextString(m) }
    13. func (*Test) ProtoMessage() {}
    14. or
    15. pb code:
    16. option (gogoproto.goproto_getters_all) = false;
    17. message test {
    18. option (gogoproto.goproto_stringer) = false;
    19. string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
    20. }
    21. go code:
    22. type Test struct {
    23. MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
    24. XXX_unrecognized []byte `json:"-"`
    25. }
    26. func (m *Test) Reset() { *m = Test{} }
    27. func (*Test) ProtoMessage() {}

    7 gogoproto.gostring

    这个选项为 message 级别,为 true 的时候,gogo 会为相应的 message 生成 GoString() 方法。如果想为所有的 message 生成之类函数,可以设置 package 级别的 gogoproto.stringer_all 为 true。

    1. pb code:
    2. option (gogoproto.goproto_getters_all) = false;
    3. message test {
    4. option (gogoproto.gostring) = true;
    5. string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
    6. }
    7. go code:
    8. type Test struct {
    9. MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
    10. XXX_unrecognized []byte `json:"-"`
    11. }
    12. func (m *Test) Reset() { *m = Test{} }
    13. func (m *Test) String() string { return proto.CompactTextString(m) }
    14. func (*Test) ProtoMessage() {}
    15. func init() {
    16. proto.RegisterEnum("test.E", E_name, E_value)
    17. }
    18. func (this *Test) GoString() string {
    19. if this == nil {
    20. return "nil"
    21. }
    22. s := strings.Join([]string{`&test.Test{` +
    23. `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg),
    24. `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ")
    25. return s
    26. }

    gogoproto.stringer_all

    1. pb code:
    2. option (gogoproto.goproto_getters_all) = false;
    3. option (gogoproto.gostring_all) = true;
    4. message test {
    5. string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"];
    6. }
    7. go code:
    8. type Test struct {
    9. MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
    10. XXX_unrecognized []byte `json:"-"`
    11. }
    12. func (m *Test) Reset() { *m = Test{} }
    13. func (m *Test) String() string { return proto.CompactTextString(m) }
    14. func (*Test) ProtoMessage() {}
    15. func init() {
    16. proto.RegisterEnum("test.E", E_name, E_value)
    17. }
    18. func (this *Test) GoString() string {
    19. if this == nil {
    20. return "nil"
    21. }
    22. s := strings.Join([]string{`&test.Test{` +
    23. `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg),
    24. `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ")
    25. return s
    26. }

    测试用例:

    1. package main
    2. import (
    3. "fmt"
    4. "strings"
    5. )
    6. type Test struct {
    7. MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"`
    8. XXX_unrecognized []byte `json:"-"`
    9. }
    10. func (this *Test) GoString() string {
    11. if this == nil {
    12. return "nil"
    13. }
    14. s := strings.Join([]string{`&test.Test{` +
    15. `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg),
    16. `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ")
    17. return s
    18. }
    19. func main() {
    20. var test Test
    21. test.MyMsg = "hello, world!"
    22. fmt.Printf("%#v\n", test)
    23. }

    8 populate & populate_all

    这个选项为 message 级别,当设置其值为 true 的时候,gogo 会为每个 message 生成一个 NewPopulated 函数。

    1. pb code:
    2. option (gogoproto.populate_all) = true;
    3. message B {
    4. optional A A = 1 [(gogoproto.nullable) = false, (gogoproto.embed) = true];
    5. repeated bytes G = 2 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uint128", (gogoproto.nullable) = false];
    6. }
    7. go code:
    8. func NewPopulatedB(r randyExample, easy bool) *B {
    9. this := &B{}
    10. v2 := NewPopulatedA(r, easy)
    11. this.A = *v2
    12. if r.Intn(10) != 0 {
    13. v3 := r.Intn(10)
    14. this.G = make([]github_com_gogo_protobuf_test_custom.Uint128, v3)
    15. for i := 0; i < v3; i++ {
    16. v4 := github_com_gogo_protobuf_test_custom.NewPopulatedUint128(r)
    17. this.G[i] = *v4
    18. }
    19. }
    20. if !easy && r.Intn(10) != 0 {
    21. this.XXX_unrecognized = randUnrecognizedExample(r, 3)
    22. }
    23. return this
    24. }

    如果 gogo 为 message 生成了 test 函数,这些函数就会调用 NewPopulated 函数。如果用户没有使用这个选项但是使用了 test 选项,则用户需自定义 NewPopulated 函数。

    由于 oschina 对博文长度有限制,剩余部分转下文《gogoprotobuf 使用 (下)》
    https://my.oschina.net/alexstocks/blog/387031