Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get the type name of a generic struct without type parameters

Tags:

generics

go

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).

like image 911
Lyngbakr Avatar asked Nov 02 '25 06:11

Lyngbakr


1 Answers

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). Reposting it here for clarity:

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]
}

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.

like image 164
blackgreen Avatar answered Nov 05 '25 03:11

blackgreen



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!