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
}
CodePudding user response:
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
CodePudding user response:
As of Go 1.19 the constraint comparable
already can be implemented only by strictly comparable types, i.e. types for which ==
and !=
are guaranteed to not panic at run time.
So this does exclude interfaces. However it also excludes other types that aren't strictly comparable but also not interfaces either, e.g. [5]any
or struct{ Data any }
.
I don't know if this is good enough for you. Otherwise, there is no way to define a constraint type set in terms of subtractions, i.e. in set notation, A \ B. From the spec:
An interface type is specified by a list of interface elements. An interface element is either a method or a type element, where a type element is a union of one or more type terms. A type term is either a single type or a single underlying type.
If you need to exclude some kinds of types from your computation you might have to fall back to reflection.
Consider also that if your goal is to unmarshal JSON, using any
constraint and *T
might be acceptable (==
checks if two pointers point to the same variable). After all, you are supposed to pass an addressable value to json.Unmarshal
:
// code simplified for illustrative purposes
func (jit *JitJSON[T]) Unmarshal() (T, error) {
var val T
err := json.Unmarshal(jit.data, &val) // needs a pointer anyway
if err != nil {
return val, err
}
jit.val = &val
return *jit.val, nil
}