I have a function that I cannot change, the function looks like foo(interface{})
. Among some other types, this function can take a type []byte
but cannot take [16]byte
. I want to write a little adapter based on generics that add support for UUIDs instead of writing foo(uuid[:])
, but I don't want to get hung up on specific implementations. For example, instead of
import (
gofrsuuid "github.com/gofrs/uuid"
googleuuid "github.com/google/uuid"
)
type AcceptedCertainTypes interface {
int | gofrsuuid.UUID | googleuuid.UUID // | etc...
}
I want to have
type AcceptedTypes interface {
int | ~[16]byte
}
But I have no idea how to do this. When we use certain types, it is easy to turn them into the right ones.
func rewrittenFoo[T AcceptedCertainTypes](val T) {
var t interface{} = *new(T)
switch t.(type) {
case gofrsuuid.UUID:
k := val.(gofrsuuid.UUID)
foo(k[:])
case googleuuid.UUID:
k := val.(googleuuid.UUID)
foo(k[:])
}
}
But how to convert interface{}
that contains gofrsuuid.UUID
to that base type [16]byte
?
CodePudding user response:
You can't have an exhaustive type switch on a union's approximate term like ~[16]byte
, because the type set by definition is unbound. You have to use reflection to extract the array type and eventually reslice it.
Only one approximate term
If the approximate term ~[16]byte
is the only one in the union, you can type-switch and handle it in the default
block. This is based on the compile-time type safety of type parameters, so that default
block will not run with any unexpected type:
func rewrittenFoo[T int | ~[16]byte](val T) {
switch t := any(val).(type) {
// handle all non-approximate type cases first
case int:
foo(t) // t is int
// this will be all other types in T's type set that are not int
// so effectively ~[16]byte
default:
v := reflect.ValueOf(t).Convert(reflect.TypeOf([16]byte{})).Interface().([16]byte)
foo(v[:])
}
}
Playground: https://go.dev/play/p/_uxmWGyEW5N
Many different approximate terms
If you have many tilde terms in a union, you can't rely on default
case. If the underlying types are all different, you may be able to switch on reflect.Kind
:
func rewrittenFoo[T int | ~float64 | ~[16]byte](val T) {
// handle all non-approximate type cases first
switch t := any(val).(type) {
case int:
foo(t)
}
switch reflect.TypeOf(val).Kind() {
case reflect.Float:
// ...
case reflect.Array:
// ...
}
}
Many similar approximate terms
Type parameters won't help much, just use any
and exhaustively type-switch an all possible types. You can group types that you know have the same underlying type and use Value#Convert
as shown above — or type-specific methods like Value#Int()
or Value#String()
—, to handle them similarly.