聲明:版權全部,謝絕轉載。git
在go中使用google protobuf,有兩個可選用的包: goprotobuf(go官方出品)和gogoprotobuf(gogo組織出品^_^)。github
gogoprotobuf可以徹底兼容google protobuf。並且通過我一些測試,它生成大代碼質量確實要比goprotobuf高一些,主要是它在goprotobuf之上extend了一些option。這些option也是有級別區分的,有的option只能修飾field,有的能夠修飾enum,有的能夠修飾message,有的是修飾package(即對整個文件都有效)。下面一一說明其一些選項的意義。golang
另外,本文的全部proto示例都是proto v3格式。json
1 gogoproto.goproto_enum_prefix 函數
若是選項爲false,則生成的代碼中不加"E_"。測試
pb code: enum E { // option (gogoproto.goproto_enum_prefix) = false; A = 0; B = 2; } go code: const ( // option (gogoproto.goproto_enum_prefix) = false; E_A E = 0 E_B E = 2 ) or pb code: enum E { // option (gogoproto.goproto_enum_prefix) = false; A = 0; B = 2; } go code: const ( A E = 0 B E = 2 )
2 gogoproto.goproto_gettersui
若是選項爲false,則不會爲message的每一個field生成一個Get函數。this
pb code: message test { // option (gogoproto.goproto_getters) = false; E e = 1; } go code: type Test struct { E *E `protobuf:"varint,1,opt,name=e,enum=test.E" json:"e,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *Test) GetE() E { if m != nil && m.E != nil { return *m.E } return A } or pb code: message test { option (gogoproto.goproto_getters) = false; E e = 1; } go code: type Test struct { E *E `protobuf:"varint,1,opt,name=e,enum=test.E" json:"e,omitempty"` XXX_unrecognized []byte `json:"-"` }
3 gogoproto.facegoogle
當這個選項爲true的時候,會爲message生成相應的interface。spa
message test { option (gogoproto.goproto_getters) = false; option (gogoproto.face) = true; string msg = 1; } type Test struct { Msg *string `protobuf:"bytes,1,opt,name=msg" json:"msg,omitempty"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} func init() { proto.RegisterEnum("test.E", E_name, E_value) } type TestFace interface { Proto() github_com_gogo_protobuf_proto.Message GetMsg() *string } func (this *Test) Proto() github_com_gogo_protobuf_proto.Message { return this } func (this *Test) TestProto() github_com_gogo_protobuf_proto.Message { return NewTestFromFace(this) } func (this *Test) GetMsg() *string { return this.Msg } func NewTestFromFace(that TestFace) *Test { this := &Test{} this.Msg = that.GetMsg() return this }
這個選項要求goproto_getters選項爲false,即只生成interface相應的method。不然,你會收到以下error:
panic: face requires getters to be disabled please use gogoproto.getters or gogoproto.getters_all and set it to false goroutine 1 [running]: github.com/gogo/protobuf/plugin/face.(*plugin).Generate(0x2086c00a0, 0x20870b780) /Users/Alex/bin/go/src/github.com/gogo/protobuf/plugin/face/face.go:164 +0x37d github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).runPlugins(0x2087001c0, 0x20870b780) /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:1008 +0x91 github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).generate(0x2087001c0, 0x20870b780) /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:1047 +0x3e1 github.com/gogo/protobuf/protoc-gen-gogo/generator.(*Generator).GenerateAllFiles(0x2087001c0) /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/generator/generator.go:994 +0x249 main.main() /Users/Alex/bin/go/src/github.com/gogo/protobuf/protoc-gen-gogo/main.go:96 +0x31d --gogo_out: protoc-gen-gogo: Plugin failed with status code 2.
4 gogoproto.nullable
有沒有注意到上面的示例中Test的成員msg類型爲string*,當你要向它賦值的時候,可能要寫出以下代碼:
var test Test test.Msg = new(string) *test.Msg = "test.msg" or test := Test{Msg:proto.String("hello")}
是否是感受很麻煩?並且生成一堆臨時對象,不利於gc。此時若是nullable選項爲false,就不用這麼麻煩了
pb code: message test { string msg = 1 [(gogoproto.nullable) = false]; } go code: type Test struct { Msg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` }
嚴格地說,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的名稱同樣。
pb code: message test { option (gogoproto.goproto_getters) = false; string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` }
相似的選項還有gogoproto.customtype,再也不贅述。
6 gogoproto.goproto_stringer
此選項不設置的時候,其爲true。當這個選項爲false的時候,gogo再也不爲message對一個的struct生成String()方法。這個選項建議不要設置爲false。
pb code: message test { option (gogoproto.goproto_stringer) = true; string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} or pb code: option (gogoproto.goproto_getters_all) = false; message test { option (gogoproto.goproto_stringer) = false; string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (*Test) ProtoMessage() {}
7 gogoproto.gostring
這個選項爲message級別,爲true的時候,gogo會爲相應的message生成GoString()方法。若是想爲全部的message生成之類函數,能夠設置package級別的gogoproto.stringer_all爲true。
pb code: option (gogoproto.goproto_getters_all) = false; message test { option (gogoproto.gostring) = true; string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} func init() { proto.RegisterEnum("test.E", E_name, E_value) } func (this *Test) GoString() string { if this == nil { return "nil" } s := strings.Join([]string{`&test.Test{` + `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg), `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ") return s }
gogoproto.stringer_all
pb code: option (gogoproto.goproto_getters_all) = false; option (gogoproto.gostring_all) = true; message test { string msg = 1 [(gogoproto.nullable) = false, (gogoproto.customname) = "MyMsg"]; } go code: type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (m *Test) Reset() { *m = Test{} } func (m *Test) String() string { return proto.CompactTextString(m) } func (*Test) ProtoMessage() {} func init() { proto.RegisterEnum("test.E", E_name, E_value) } func (this *Test) GoString() string { if this == nil { return "nil" } s := strings.Join([]string{`&test.Test{` + `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg), `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ") return s }
測試用例:
package main import ( "fmt" "strings" ) type Test struct { MyMsg string `protobuf:"bytes,1,opt,name=msg" json:"msg"` XXX_unrecognized []byte `json:"-"` } func (this *Test) GoString() string { if this == nil { return "nil" } s := strings.Join([]string{`&test.Test{` + `MyMsg:` + fmt.Sprintf("%#v", this.MyMsg), `XXX_unrecognized:` + fmt.Sprintf("%#v", this.XXX_unrecognized) + `}`}, ", ") return s } func main() { var test Test test.MyMsg = "hello, world!" fmt.Printf("%#v\n", test) }
8 populate & populate_all
這個選項爲message級別,當設置其值爲true的時候,gogo會爲每一個message生成一個NewPopulated函數。
pb code: option (gogoproto.populate_all) = true; message B { optional A A = 1 [(gogoproto.nullable) = false, (gogoproto.embed) = true]; repeated bytes G = 2 [(gogoproto.customtype) = "github.com/gogo/protobuf/test/custom.Uint128", (gogoproto.nullable) = false]; } go code: func NewPopulatedB(r randyExample, easy bool) *B { this := &B{} v2 := NewPopulatedA(r, easy) this.A = *v2 if r.Intn(10) != 0 { v3 := r.Intn(10) this.G = make([]github_com_gogo_protobuf_test_custom.Uint128, v3) for i := 0; i < v3; i++ { v4 := github_com_gogo_protobuf_test_custom.NewPopulatedUint128(r) this.G[i] = *v4 } } if !easy && r.Intn(10) != 0 { this.XXX_unrecognized = randUnrecognizedExample(r, 3) } return this }
若是gogo爲message生成了test函數,這些函數就會調用NewPopulated函數。若是用戶沒有使用這個選項可是使用了test選項,則用戶需自定義NewPopulated函數。
因爲oschina對博文長度有限制,剩餘部分轉下文《gogoprotobuf使用(下)》。