Search code examples
gogenericsreflection

Get type parameter from a generic struct using reflection


Imagine I have the following struct:

type MyGeneric[T string | int] struct {
}

I want to check whether the generic used to instantiate that struct was a string or a int when creating a new MyGeneric.

myGenericString := MyGeneric[string]{}
myGenericString.canHandle("hello") -> should return true
myGenericString.canHandle(8) -> should return false

func (mG MyGeneric[T]) canHandle(value any) bool {
    // how to get what T is the same type as value
}


Solution

  • For normal values, just instantiate the T directly to get its value and reflect.TypeOf() it. But we can declare a [0]T and take its element type instead, because:

    • [0]T takes 0 memory while T may be as large as T, which would waste a lot of stack and heap 1 if T is something like [4096]int.
    • A raw T does not work with interface types. reflect.TypeOf() on a nil interface value (whether empty interface or not) will return reflect.Type(nil), of which subsequent uses will cause panic.
    package main
    
    import (
        "fmt"
        "reflect"
    )
    
    type MyGeneric[T any] struct {
    }
    
    func (mG MyGeneric[T]) canHandle(value any) bool {
        var zero [0]T
        tt := reflect.TypeOf(zero).Elem()
        vt := reflect.TypeOf(value)
    
        fmt.Printf("-> %v covers %v\n", tt, vt)
        return vt.AssignableTo(tt)
    }
    
    type empty struct{}
    
    func main() {
        fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(""))
        fmt.Printf("%v\n", MyGeneric[any]{}.canHandle(""))
        fmt.Printf("%v\n", MyGeneric[string]{}.canHandle(1))
        fmt.Printf("%v\n", MyGeneric[MyGeneric[struct{}]]{}.canHandle(MyGeneric[struct{}]{}))
        fmt.Printf("%v\n", MyGeneric[MyGeneric[struct{}]]{}.canHandle(MyGeneric[empty]{}))
    }
    

    Output:

    -> string covers string
    true
    -> interface {} covers string
    true
    -> string covers int
    false
    -> main.MyGeneric[struct {}] covers main.MyGeneric[struct {}]
    true
    -> main.MyGeneric[struct {}] covers main.MyGeneric[main.empty]
    false
    

    1 Not sure if it can be optimized out or whether it allocates on heap or stack or both, because it is passed into reflect.TypeOf after upcasting to an interface{}.