前言
本文通过分析 kitex 根据 IDL 生成的代码学习 Thrift required、optional、default 的区别。
前置条件
IDL
namespace go test
struct Data {
1: required i64 Id,
2: required string Content,
}
struct Request {
1: required i64 Id,
}
struct Response {
1: required Data Data,
// 1: optional Data Data,
// 1: Data Data,
// 1: required list<Data> Data,
// 1: optional list<Data> Data,
// 1: list<Data> Data,
}
service Service {
Response Get (1: Request req),
}
代码生成命令
代码生产工具:kitex
版本:v1.10.1
kitex -module module_name -service p.s.m idl/test.thrift
Generate code diff
required Data
type Response struct {
Data *Data `thrift:"Data,1,required" json:"Data"`
}
func (p *Response) Read(iprot thrift.TProtocol) (err error) {
var fieldTypeId thrift.TType
var fieldId int16
var issetData bool = false // Part of the more than optional、default
if _, err = iprot.ReadStructBegin(); err != nil {
goto ReadStructBeginError
}
for {
_, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
if err != nil {
goto ReadFieldBeginError
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if fieldTypeId == thrift.STRUCT {
if err = p.ReadField1(iprot); err != nil {
goto ReadFieldError
}
issetData = true // Part of the more than optional、default
} else {
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
default:
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
if err = iprot.ReadFieldEnd(); err != nil {
goto ReadFieldEndError
}
}
if err = iprot.ReadStructEnd(); err != nil {
goto ReadStructEndError
}
if !issetData { // Part of the more than optional、default
fieldId = 1 // Part of the more than optional、default
goto RequiredFieldNotSetError // Part of the more than optional、default
} // Part of the more than optional、default
return nil
ReadStructBeginError:
return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
ReadFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
ReadFieldError:
return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_Response[fieldId]), err)
SkipFieldError:
return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
ReadFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
ReadStructEndError:
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
RequiredFieldNotSetError: // Part of the more than optional、default
return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("required field %s is not set", fieldIDToName_Response[fieldId])) // Part of the more than optional、default
}
optional Data
type Response struct {
Data *Data `thrift:"Data,1,optional" json:"Data,omitempty"`
}
func (p *Response) writeField1(oprot thrift.TProtocol) (err error) {
if p.IsSetData() { // Part of the more than required、default
if err = oprot.WriteFieldBegin("Data", thrift.STRUCT, 1); err != nil {
goto WriteFieldBeginError
}
if err := p.Data.Write(oprot); err != nil {
return err
}
if err = oprot.WriteFieldEnd(); err != nil {
goto WriteFieldEndError
}
} // Part of the more than required、default
return nil
WriteFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
WriteFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
}
default Data
type Response struct {
Data *Data `thrift:"Data,1" json:"Data"`
}
Write
required Data
func (p *Response) Write(oprot thrift.TProtocol) (err error) {
var fieldId int16 // 字段 Id
// 写 StructBegin 标记,这里的 Struct 是 Response
if err = oprot.WriteStructBegin("Response"); err != nil {
goto WriteStructBeginError
}
if p != nil {
// 依次写 Struct Response 的 field
if err = p.writeField1(oprot); err != nil {
fieldId = 1
goto WriteFieldError
}
}
// 写「FieldStop 标记」
if err = oprot.WriteFieldStop(); err != nil {
goto WriteFieldStopError
}
// 写「StructEnd 标记」
if err = oprot.WriteStructEnd(); err != nil {
goto WriteStructEndError
}
return nil
WriteStructBeginError:
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
WriteFieldError:
return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
WriteFieldStopError:
return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
WriteStructEndError:
return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
}
// 写「Id 为 1」的字段的方法
func (p *Response) writeField1(oprot thrift.TProtocol) (err error) {
// 写 FieldBegin 标记,带字段类型 Id,字段 Id
if err = oprot.WriteFieldBegin("Data", thrift.STRUCT, 1); err != nil {
goto WriteFieldBeginError
}
// 调用 Struct Data 的 Write 方法
if err := p.Data.Write(oprot); err != nil {
return err
}
// 写 FieldEnd 标记
if err = oprot.WriteFieldEnd(); err != nil {
goto WriteFieldEndError
}
return nil
WriteFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
WriteFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
}
// Struct Data 的 Write 方法
func (p *Data) Write(oprot thrift.TProtocol) (err error) {
var fieldId int16
if err = oprot.WriteStructBegin("Data"); err != nil {
goto WriteStructBeginError
}
if p != nil {
if err = p.writeField1(oprot); err != nil {
fieldId = 1
goto WriteFieldError
}
if err = p.writeField2(oprot); err != nil {
fieldId = 2
goto WriteFieldError
}
}
if err = oprot.WriteFieldStop(); err != nil {
goto WriteFieldStopError
}
if err = oprot.WriteStructEnd(); err != nil {
goto WriteStructEndError
}
return nil
WriteStructBeginError:
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
WriteFieldError:
return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
WriteFieldStopError:
return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
WriteStructEndError:
return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
}
// 基本类型(I64、STRING...)直接写
func (p *Data) writeField1(oprot thrift.TProtocol) (err error) {
if err = oprot.WriteFieldBegin("Id", thrift.I64, 1); err != nil {
goto WriteFieldBeginError
}
if err := oprot.WriteI64(p.Id); err != nil {
return err
}
if err = oprot.WriteFieldEnd(); err != nil {
goto WriteFieldEndError
}
return nil
WriteFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
WriteFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
}
optional Data
func (p *Response) writeField1(oprot thrift.TProtocol) (err error) {
// 与 required 的区别是会判断字段是否设置
if p.IsSetData() {
if err = oprot.WriteFieldBegin("Data", thrift.STRUCT, 1); err != nil {
goto WriteFieldBeginError
}
if err := p.Data.Write(oprot); err != nil {
return err
}
if err = oprot.WriteFieldEnd(); err != nil {
goto WriteFieldEndError
}
}
return nil
WriteFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
WriteFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
}
func (p *Response) IsSetData() bool {
return p.Data != nil
}
default Data
与 required 相同。
Read
required Data
func (p *Response) Read(iprot thrift.TProtocol) (err error) {
var fieldTypeId thrift.TType // 字段类型 Id
var fieldId int16 // 字段 Id
var issetData bool = false // required 字段(Data)「是否设置成功」标记
// 读取 StructBegin 标记,这里的 Struct 是 Response
if _, err = iprot.ReadStructBegin(); err != nil {
goto ReadStructBeginError
}
// 循环读取 Struct Response 的 field
for {
// 读取 FieldBegin 标记,拿到字段类型 Id,字段 Id
_, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
if err != nil {
goto ReadFieldBeginError
}
// 读取到「FieldStop 标记」跳出循环
if fieldTypeId == thrift.STOP {
break
}
// 匹配 字段 Id
switch fieldId {
case 1:
// 字段 Id 匹配后判断「字段类型 Id」是否匹配
if fieldTypeId == thrift.STRUCT {
// 调用读取「Id 为 1」的字段的方法
if err = p.ReadField1(iprot); err != nil {
goto ReadFieldError
}
// required 字段(Data)设置成功,标记置为 true
issetData = true
} else { // 字段类型 Id 不匹配,跳过
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
default: // 字段 Id 不匹配,跳过
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
// 读取 FieldEnd 标记
if err = iprot.ReadFieldEnd(); err != nil {
goto ReadFieldEndError
}
}
// 读取 StructEnd 标记,这里的 Struct 是 Response
if err = iprot.ReadStructEnd(); err != nil {
goto ReadStructEndError
}
// 如果 required 字段(Data)没有设置成功,报错
if !issetData {
fieldId = 1
goto RequiredFieldNotSetError
}
return nil
ReadStructBeginError:
return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
ReadFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
ReadFieldError:
return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_Response[fieldId]), err)
SkipFieldError:
return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
ReadFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
ReadStructEndError:
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
RequiredFieldNotSetError:
return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("required field %s is not set", fieldIDToName_Response[fieldId]))
}
// 读取「Id 为 1」的字段的方法
func (p *Response) ReadField1(iprot thrift.TProtocol) error {
p.Data = NewData()
// 调用 Struct Data 的 Read 方法
if err := p.Data.Read(iprot); err != nil {
return err
}
return nil
}
// Struct Data 的 Read 方法
func (p *Data) Read(iprot thrift.TProtocol) (err error) {
var fieldTypeId thrift.TType
var fieldId int16
var issetId bool = false
var issetContent bool = false
if _, err = iprot.ReadStructBegin(); err != nil {
goto ReadStructBeginError
}
for {
_, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
if err != nil {
goto ReadFieldBeginError
}
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if fieldTypeId == thrift.I64 {
if err = p.ReadField1(iprot); err != nil {
goto ReadFieldError
}
issetId = true
} else {
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
case 2:
if fieldTypeId == thrift.STRING {
if err = p.ReadField2(iprot); err != nil {
goto ReadFieldError
}
issetContent = true
} else {
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
default:
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
if err = iprot.ReadFieldEnd(); err != nil {
goto ReadFieldEndError
}
}
if err = iprot.ReadStructEnd(); err != nil {
goto ReadStructEndError
}
if !issetId {
fieldId = 1
goto RequiredFieldNotSetError
}
if !issetContent {
fieldId = 2
goto RequiredFieldNotSetError
}
return nil
ReadStructBeginError:
return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
ReadFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
ReadFieldError:
return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_Data[fieldId]), err)
SkipFieldError:
return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
ReadFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
ReadStructEndError:
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
RequiredFieldNotSetError:
return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("required field %s is not set", fieldIDToName_Data[fieldId]))
}
// 基本类型(I64、STRING...)直接读取
func (p *Data) ReadField1(iprot thrift.TProtocol) error {
if v, err := iprot.ReadI64(); err != nil {
return err
} else {
p.Id = v
}
return nil
}
optional Data
与 required 的区别是没有 issetX 的校验。(X 为 required 字段)
default Data
与 optional 相同。
Case analysis
required 基本类型不赋值为什么可以正常通信?
基本类型有默认值,没有对默认值做判断(默认值不代表空)。
是否可以做到 required 基本类型不赋值不能通信?
猜想:生成代码时生成基本类型的指针类型
// Struct Data 的 Write 方法
func (p *Data) Write(oprot thrift.TProtocol) (err error) {
var fieldId int16
if err = oprot.WriteStructBegin("Data"); err != nil {
goto WriteStructBeginError
}
if p != nil {
// 判断基本类型字段为 nil 时不写入(Data Read 时就会报错 "required field Id is not set")
if p.Id != nil {
if err = p.writeField1(oprot); err != nil {
fieldId = 1
goto WriteFieldError
}
}
if p.Content != nil {
if err = p.writeField2(oprot); err != nil {
fieldId = 2
goto WriteFieldError
}
}
}
if err = oprot.WriteFieldStop(); err != nil {
goto WriteFieldStopError
}
if err = oprot.WriteStructEnd(); err != nil {
goto WriteStructEndError
}
return nil
WriteStructBeginError:
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
WriteFieldError:
return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
WriteFieldStopError:
return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
WriteStructEndError:
return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
}
required 结构体类型不赋值为什么不可以正常通信?
required 结构体类型生成的代码字段类型为结构体指针,默认值为 nil
// Struct Data 的 Write 方法
func (p *Data) Write(oprot thrift.TProtocol) (err error) {
var fieldId int16
if err = oprot.WriteStructBegin("Data"); err != nil {
goto WriteStructBeginError
}
// 写入时没有再继续写入结构体下的字段
if p != nil {
if err = p.writeField1(oprot); err != nil {
fieldId = 1
goto WriteFieldError
}
if err = p.writeField2(oprot); err != nil {
fieldId = 2
goto WriteFieldError
}
}
// 写 FieldStop 标记
if err = oprot.WriteFieldStop(); err != nil {
goto WriteFieldStopError
}
if err = oprot.WriteStructEnd(); err != nil {
goto WriteStructEndError
}
return nil
WriteStructBeginError:
return thrift.PrependError(fmt.Sprintf("%T write struct begin error: ", p), err)
WriteFieldError:
return thrift.PrependError(fmt.Sprintf("%T write field %d error: ", p, fieldId), err)
WriteFieldStopError:
return thrift.PrependError(fmt.Sprintf("%T write field stop error: ", p), err)
WriteStructEndError:
return thrift.PrependError(fmt.Sprintf("%T write struct end error: ", p), err)
}
// Struct Data 的 Read 方法
func (p *Data) Read(iprot thrift.TProtocol) (err error) {
var fieldTypeId thrift.TType
var fieldId int16
var issetId bool = false
var issetContent bool = false
if _, err = iprot.ReadStructBegin(); err != nil {
goto ReadStructBeginError
}
for {
// 读取字段时直接读取到 FieldStop 标记
_, fieldTypeId, fieldId, err = iprot.ReadFieldBegin()
if err != nil {
goto ReadFieldBeginError
}
// 跳出循环
if fieldTypeId == thrift.STOP {
break
}
switch fieldId {
case 1:
if fieldTypeId == thrift.I64 {
if err = p.ReadField1(iprot); err != nil {
goto ReadFieldError
}
// 未赋值
issetId = true
} else {
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
case 2:
if fieldTypeId == thrift.STRING {
if err = p.ReadField2(iprot); err != nil {
goto ReadFieldError
}
// 未赋值
issetContent = true
} else {
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
default:
if err = iprot.Skip(fieldTypeId); err != nil {
goto SkipFieldError
}
}
if err = iprot.ReadFieldEnd(); err != nil {
goto ReadFieldEndError
}
}
if err = iprot.ReadStructEnd(); err != nil {
goto ReadStructEndError
}
// 满足条件报错 "required field Id is not set"
if !issetId {
fieldId = 1
goto RequiredFieldNotSetError
}
if !issetContent {
fieldId = 2
goto RequiredFieldNotSetError
}
return nil
ReadStructBeginError:
return thrift.PrependError(fmt.Sprintf("%T read struct begin error: ", p), err)
ReadFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T read field %d begin error: ", p, fieldId), err)
ReadFieldError:
return thrift.PrependError(fmt.Sprintf("%T read field %d '%s' error: ", p, fieldId, fieldIDToName_Data[fieldId]), err)
SkipFieldError:
return thrift.PrependError(fmt.Sprintf("%T field %d skip type %d error: ", p, fieldId, fieldTypeId), err)
ReadFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T read field end error", p), err)
ReadStructEndError:
return thrift.PrependError(fmt.Sprintf("%T read struct end error: ", p), err)
RequiredFieldNotSetError:
return thrift.NewTProtocolExceptionWithType(thrift.INVALID_DATA, fmt.Errorf("required field %s is not set", fieldIDToName_Data[fieldId]))
}
required list 类型生成的 Slice 为 nil 时为什么可以正常通信?
func (p *Response) writeField1(oprot thrift.TProtocol) (err error) {
if err = oprot.WriteFieldBegin("Data", thrift.LIST, 1); err != nil {
goto WriteFieldBeginError
}
// len(p.Data) == len(nil) == 0
if err := oprot.WriteListBegin(thrift.STRUCT, len(p.Data)); err != nil {
return err
}
// range nil 不会进入循环
for _, v := range p.Data {
if err := v.Write(oprot); err != nil {
return err
}
}
if err := oprot.WriteListEnd(); err != nil {
return err
}
if err = oprot.WriteFieldEnd(); err != nil {
goto WriteFieldEndError
}
return nil
WriteFieldBeginError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 begin error: ", p), err)
WriteFieldEndError:
return thrift.PrependError(fmt.Sprintf("%T write field 1 end error: ", p), err)
}
func (p *Response) ReadField1(iprot thrift.TProtocol) error {
// size == 0
_, size, err := iprot.ReadListBegin()
if err != nil {
return err
}
p.Data = make([]*Data, 0, size)
for i := 0; i < size; i++ {
_elem := NewData()
if err := _elem.Read(iprot); err != nil {
return err
}
p.Data = append(p.Data, _elem)
}
if err := iprot.ReadListEnd(); err != nil {
return err
}
return nil
}
写端 required 结构体字段为 nil 时,读端可以不报错吗?
猜想:读端 required 结构体内的字段都设置为 optional。
......
以上仅为个人学习总结,欢迎交流指正。