Is there a way to type check within a generic function without reflection?

Viewed 47

In the case of a generic function that does byte serialization for a generic types, is there a way to proceed--other than reflection--if the different supported types have different sizes? For example:

package main

import (
    "fmt"
)   

type KeyType interface {
    uint16 | uint32 | uint64
}   

type Item[KT KeyType] struct {
    Key  KT  
    Data []byte
}   

// set of generic types that hold collections of Item[T]
// sets of methods that operate on those generic types

func MarshalBinary[KT KeyType](i *Item[KT]) ([]byte, error) {
    // How do I compute the size of the item and marshal it?
    // It's 2 bytes for uint16, 4 for uint32, 8 for uint64,
    // how do I distinguish here?
}   

func main() {
    i := new(Item[uint32])
    i.Key = 42
    fmt.Println(i)
}   

Is there a way to access the size of the type within the serialization function without reflection?

I know I can proceed with reflection like this:

package main

import (
    "fmt"
    "reflect"
    "strings"
)

type KeyType interface {
    uint16 | uint32 | uint64
}

type Item[KT KeyType] struct {
    Key  KT
    Data []byte
}

// set of generic types that hold collections of Item[T]
// sets of methods that operate on those generic types

func MarshalBinary[KT KeyType](i *Item[KT]) ([]byte, error) {
    t := reflect.TypeOf(i)
    var size int
    if strings.Contains(t.String(), `uint32`) {
        size = 4
    }
    fmt.Println(size)
    // rest of function here
    return nil, nil
}

func main() {
    i := new(Item[uint32])
    i.Key = 42
    MarshalBinary(i)
    fmt.Println(i)
}

Is there a better way? My main concern with using reflection here is the potential performance cost.

1 Answers

First off, I think your sample code may be incorrect, because you're using reflect.TypeOf(i), but i is of type Item[KT], and since Item includes both a KT and a []byte, it will be the size of KY plus the size of a pointer (the pointer to the byte slice). So, it will be 4 + pointer size if KT is a uint32, but you're setting it to 4.

So the question is, are you trying to get the size of i (Item[KT]), or the size of an instance of KT?

I assume it's the size of KT that you're actually looking for, since you're assigning a size of 4 if it's a uint32. You can do this without reflect by casting the value you want the size of as an interface{}, then using a standard type switch, as follows:

func MarshalBinary[KT KeyType](i *Item[KT]) ([]byte, error) {
    var size int
    switch (interface{})(i.Key).(type) {
    case uint16:
        size = 2
    case uint32:
        size = 4
    case uint64:
        size = 8
    default:
        panic("Unexpected type")
    }
    ...
}

This is a bit problematic if you ever want to expand the different possible types that KeyType could be, though.

Related