-
-
Notifications
You must be signed in to change notification settings - Fork 2
(RU) Decoding walkthrough
Довольно часто в определении методов можно встретить такую ситуацию:
// Bool is a special case, we don't need to worry about it.
messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool;
// Update is more interesting case: как нам понять, какой конструктор нужно
// сформировать? ведь в go интерфейсы не имеют своих методов!
getUpdates#25d6c9c7 channel_id:int max_id:int = Updates;
Если с первым все просто и понятно (попробуй crc код от true а потом false), то с комплексными типами не понятно — как понять какую структуру нам нужно использовать? где найти ее инстанс? Мы будем искать перебором? А если нам попадется некорректный crc код?
На самом деле существует два теоретических способа избавиться от таких проблем:
- Использовать полный регистр всех возможных crc кодов, что бы упростить сам код
- Использовать конструкторы для каждого интерфейса, использовать только регистр конструкторов
У каждого способа есть свои плюсы и минусы, о которых мы расскажем потом, однако сейчас мы постараемся описать как решить эту проблему.
Итак, предположим нам нужно имплементировать такие типы:
updatesTooLong#e317af7e = Updates;
updateShort#12345678 = Updates;
updateShortMessage#78d4dec1 ... = Updates;
updateShortChatMessage#402d5dbb ... = Updates;
Мы могли бы завести регистр всех типов, и искать объекты по глобальному регистру. Однако, мы понимаем, что это не самый лучший вариант, поэтому давайте создадим конструктор, который позволит нам сгенерировать инстанс объекта для того, что бы в него записать наши данные:
type Updates interface {
tl.Object
_Updates()
}
func NewUpdatesByCRC(crc uint32) Updates {
switch crc {
// ↓↓↓ Like in type assertions, go isn't allocating type here.
case (*UpdateShort)(nil).CRC():
return &UpdateShort{}
case (*UpdateShortChatMessage)(nil).CRC():
return &UpdateShortChatMessage{}
case uint32(UpdatesTooLong),
uint32(UpdatesTooShort):
return UpdatesEnum(crc)
default:
return nil
}
}
type UpdateShort struct {
...
}
func (*UpdateShort) CRC() uint32 { return 0x78d4dec1 }
func (*UpdateShort) _Updates() {}
type UpdateShortChatMessage struct {
...
}
func (*UpdateShortChatMessage) CRC() uint32 { return 0x402d5dbb }
func (*UpdateShortChatMessage) _Updates() {}
type UpdateShortMessage struct {
...
}
// for constructors which have no flags, we can create them as enums
type UpdatesEnum uint32
const (
UpdatesTooLong UpdatesEnum = 0xe317af7e
UpdatesTooShort UpdatesEnum = 0x12345678
)
func (e UpdatesEnum) CRC() uint32 { return uint32(e) }
func (_ UpdatesEnum) _Updates() {}
Ok, мы объявили все типы, все конструкторы, как же нам теперь получить интерфейс из ответа?
Мы могли бы так же сделать декодер примерно такого формата:
tl.Unmarshal(data, NewUpdatesByCRC).(Update)
Но это помимо того что не безопасно, так еще и не go-way:
var obj Update
json.Unmarshal(data, obj)
xml.Unmarshal(data, obj)
gob.Unmarshal(data, obj)
tl.Unmarshal(data, NewUpdatesByCRC).(Update) // Wtf??? 😨
Так что это выглядит некрасиво.
Для этого есть тип AnyConstructor
AnyConstructor[T]
это одно из решений, как можно ограничить набор возможных значений, даже не применяя регистр или что-то подобное. Мы просто можем передать тип вместе с конструктором! (Thank you, generics🤗)
func UseIt(u Update) {
...
}
obj := &tl.AnyConstructor[Update]{
Constructor: NewUpdatesByCRC,
}
tl.Unmarshal(data, obj) // Yeah, that's better 😉
// Yaaay! Guaranteed to be Update, if tl message is incorrect, unmarshaler reports it, not golang!
UseIt(obj.Result)
Some footer