Home > OS >  Get the type name of a generic struct without type parameters
Get the type name of a generic struct without type parameters

Time:12-20

Say I have a generic struct called foo and I create two objects from it. I can determine the concrete type of each using reflect.TypeOf(), like so:

package main

import (
    "fmt"
    "reflect"
)

type foo[T any] struct {
    data T
}

func main() {
    a := foo[string]{"cheese"}
    b := foo[int]{42}

    fmt.Println(reflect.TypeOf(a))
    fmt.Println(reflect.TypeOf(b))
}

// main.foo[string]
// main.foo[int]

What I am interested in is determining just the generic type of these objects (i.e., foo) and not the concrete type (i.e., foo[string] and foo[int]). Is this possible or do I need to manually extract the generic type from these strings (e.g., with regex)?


Edit

Regex might look something like this:

func GetGenericType(x any) string {
    // Get type as a string
    s := reflect.TypeOf(x).String()

    // Regex to run
    r := regexp.MustCompile(`\.(.*)\[`)

    // Return capture
    return r.FindStringSubmatch(s)[1]
}


fmt.Println(GetGenericType(a))
fmt.Println(GetGenericType(b))

// foo
// foo


I've also seen this question but this doesn't answer this question because it gives the concrete type (i.e., main.foo[string]) rather than the generic type (i.e., foo).

CodePudding user response:

Reflection doesn't see the name of the "base" generic type, because at run time that base type doesn't exist.

The relevant passage from the Go spec is Instantiations:

Instantiating a type results in a new non-generic named type; instantiating a function produces a new non-generic function.

So when you write:

b := foo[int]{42}
name := reflect.TypeOf(b).Name()

the name of that type is precisely foo[int].

It's worth noting that the identifier foo without the type parameter list is relevant at compile time, because it prevents you from redeclaring it in the same package. Type definitions:

A type definition creates a new, distinct type with the same underlying type and operations as the given type and binds an identifier, the type name, to it.

TypeDef = identifier [ TypeParameters ] Type .

But instantiations, as defined above, result in a new named type which is different than foo; and at run time when you can use reflection, you deal with instantiations only.

In conclusion, I think your solution with regex is acceptable, until some helper function is added to the stdlib (if ever).

Just keep in mind the difference between Type.String() and Type.Name(): any type can have a string representation, but only named types have a name. (Obviously, right?). So for example if you wrote:

b := &foo[int]{42}

then the type of b is *foo[int], which is an anonymous composite type, and Name() returns an empty string.

  • Related