Thrift - required VS optional VS default in Go

2,042 阅读7分钟

前言

本文通过分析 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。

......

以上仅为个人学习总结,欢迎交流指正。