Open8
go/protobufを読む
githubのミラーの方が読み易そう
NoUnkeyedLiteralsはなんでいるのか?
あらゆるstructの定義にNoUnkeyedLiterals
が入ってるのけどなんでか分かってない。
Varintへのエンコーディングとデコーディング
/encoding/protowire/wire.goを見ると AppendVarint
でエンコーディングし、ConsumeVarint
でデコーディングするっぽい。最適化の関係なのか、ベタ書きになってるけど、次のように書くと意味がとりやすいかも。
func AppendVarint(b []byte, v uint64) []byte {
for i := 0; i < 10; i++ {
if v < 1<<(7*(i+1)) || i == 9 {
n := i + 1
bb := make([]byte, n)
for i := 0; i < (n - 1); i++ {
bb[i] = byte(v>>(7*i)&0x7f | 0x80)
}
if i < 9 {
bb[n-1] = byte(v >> (7 * i))
} else {
bb[n-1] = 1
}
b = append(b, bb...)
break
}
}
return b
}
func ConsumeVarint(b []byte) (uint64, int) {
var v uint64
n := len(b)
for i := 0; i < 10; i++ {
if n == i {
return 0, errCodeTruncated
}
y := uint64(b[i])
v += y << (7 * i)
if i != 9 {
if y < 0x80 {
return v, (i + 1)
}
v -= 0x80 << (7 * i)
} else if y < 2 {
return v, (i + 1)
}
}
return 0, errCodeOverflow
}
7bit毎に分割して、後続ありかどうかを0x80
のフラグで管理している。64bitだと偶然7bitで分割すると、割と綺麗になる。次の要して7bit分割したのに1足すとUInt64の最大値が作れる。
var a uint64
for i := 0; i < 9; i++ {
a += 0x7f << (7 * i)
}
a += 1 << 63
fmt.Println(a)
fmt.Println(a == math.MaxUint64)// => true
実はencoding/binary
のUvarint
とPutUvarint
を使っても良さそう。
unmarshalSeedの読み飛ばし
b = b[n+m:]
してるので、中身は読まないで名前だけ取っている。中身までパースするのはunmarshalFull
の場合で、Marshal
呼ばれた時に実行される。
匿名の構造体
次のように複数箇所で匿名の構造体が定義されている。
MessageインタフェースのProtoMethods() *methods
のmethods
をprotoiface.Methods
でProtoMethods
を定義してもMessage
になれる(多分匿名だから)
実際、次の箇所で定義されている(*messageState
はMessage
になっている)
タグ
protoc-gen-go
から生成されるメッセージは次のような感じだけど
type Str struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value string `protobuf:"bytes,1,opt,name=value,proto3"`
}
レガシーモードでしか関係なくて、次のようにしても影響ない。
type Str struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value string `protobuf:"1"`
}
次のような感じにしてメッセージの順序を取ってる。
ごく初歩的な修正PRを送ってマージされた