gogoprotobuf使用(上)

  聲明:版權全部,謝絕轉載。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使用(下)》

相關文章
相關標籤/搜索