Generic type which isn't of an interface type

Issue

I’m trying to work with generics to create a parameterised type which can be of:

T, *T, T[], map[interface{}]interface{}

Where: T is of comparable type, but is not an interface.


I’ve attempted to formulate this through constrained typeset, but this fails due to MisplacedTypeParam compiler error:

type myType[T comparable] interface {
    T | *T | T[] | map[interface{}]interface{}
}

I also have the issue when using reflect, that getting the reflect.Kind
or reflect.Type of an interface will return the value’s type underlying the interface, which means I haven’t figured out how to assert the type is not an interface.


From this, I am wondering what the best alternative way to represent such a type would be?


This is my work in progress (https://github.com/mcwalrus/go-jitjson) and its main parts:

type JitJSON[T any] struct {
    data []byte
    val  *T
}

func (jit *JitJSON[T]) Unmarshal() (T, error) {
    if jit.val != nil {
        return *jit.val, nil
    }

    var val T
    if jit.data == nil {
        return val, nil
    }

    jit.val = &val
    err := json.Unmarshal(jit.data, jit.val)
    if err != nil {
        return val, err
    }

    return *jit.val, nil
}

Solution

Unfortunately you cannot** implement this simply as a type constraint. Therefore, you must rely on run-time checks.

You may implement the type check through reflect, but this may be redundant in your application, as the standard library json already produces an appropriate error when trying to marshal or unmarshal an incompatible data type.

If you do want to use reflect to inspect the exact type of a parameterized type, here’s the naive but incorrect way to do it:

func f[T any]() {
    var zero T
    typ := reflect.TypeOf(zero)
    fmt.Println(typ)
}

This works for most types, except for interface types, for which it will always report typ as <nil>. This is because of how assignment of interface values works in Go. Here is the correct way to do it:

func f[T any]() {
    var zero T
    typ := reflect.TypeOf(&zero).Elem()
    fmt.Println(typ)
}

This is necessary because interface values, at runtime, only carry type information of the concrete type which they reference. By taking the pointer to an interface variable, taking the type of the pointer, then taking the element of the pointer type, the actual type of the interface variable can be extracted.


** I can’t seem to find a concise explanation of why this can’t be done, but here are some difficulties:

  • comparable cannot be used in a type union
  • type constraints cannot be composed
  • type constraints cannot be defined recursively
  • type constraints cannot be negated
  • there is no interface which matches structs in general

Answered By – Hymns For Disco

Answer Checked By – Clifford M. (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.