Getting a base type from interface when using generics - go

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?

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.

Related

go generics: how to declare a type parameter compatible with another type parameter

I'm looking for a way to declare type compatibility between type parameters in Go generics constraints.
More specifically, I need to say some type T is compatible with another type U. For instance, T is a pointer to a struct that implements the interface U.
Below is a concrete example of what I want to accomplish:
NOTE: Please, do not answer with alternative ways to implement "array prepend". I've only used it as a concrete application of the problem I'm looking to solve. Focusing on the specific example digresses the conversation.
func Prepend[T any](array []T, values ...T) []T {
if len(values) < 1 { return array }
result := make([]T, len(values) + len(array))
copy(result, values)
copy(result[len(values):], array)
return result
}
The above function can be called to append elements of a given type T to an array of the same type, so the code below works just fine:
type Foo struct{ x int }
func (self *Foo) String() string { return fmt.Sprintf("foo#%d", self.x) }
func grow(array []*Foo) []*Foo {
return Prepend(array, &Foo{x: len(array)})
}
If the array type is different than the elements being added (say, an interface implemented by the elements' type), the code fails to compile (as expected) with type *Foo of &Foo{…} does not match inferred type Base for T:
type Base interface { fmt.Stringer }
type Foo struct{ x int }
func (self *Foo) String() string { return fmt.Sprintf("foo#%d", self.x) }
func grow(array []Base) []Base {
return Prepend(array, &Foo{x: len(array)})
}
The intuitive solution to that is to change the type parameters for Prepend so that array and values have different, but compatible types. That's the part I don't know how to express in Go.
For instance, the code below doesn't work (as expected) because the types of array and values are independent of each other. Similar code would work with C++ templates since the compatibility is validated after template instantiation (similar to duck typing). The Go compiler gives out the error invalid argument: arguments to copy result (variable of type []A) and values (variable of type []T) have different element types A and T:
func Prepend[A any, T any](array []A, values ...T) []A {
if len(values) < 1 { return array }
result := make([]A, len(values) + len(array))
copy(result, values)
copy(result[len(values):], array)
return result
}
I've tried making the type T compatible with A with the constraint ~A, but Go doesn't like a type parameter used as type of a constraint, giving out the error type in term ~A cannot be a type parameter:
func Prepend[A any, T ~A](array []A, values ...T) []A {
What's the proper way to declare this type compatibility as generics constraints without resorting to reflection?
This is a limitation of Go's type parameter inference, which is the system that tries to automatically insert type parameters in cases where you don't define them explicitly. Try adding in the type parameter explicitly, and you'll see that it works. For example:
// This works.
func grow(array []Base) []Base {
return Prepend[Base](array, &Foo{x: len(array)})
}
You can also try explicitly converting the *Foo value to a Base interface. For example:
// This works too.
func grow(array []Base) []Base {
return Prepend(array, Base(&Foo{x: len(array)}))
}
Explanation
First, you should bear in mind that the "proper" use of type parameters is to always include them explicitly. The option to omit the type parameter list is considered a "nice to have", but not intended to cover all use cases.
From the blog post An Introduction To Generics:
Type inference in practice
The exact details of how type inference works are complicated, but using it is not: type inference either succeeds or fails. If it succeeds, type arguments can be omitted, and calling generic functions looks no different than calling ordinary functions. If type inference fails, the compiler will give an error message, and in those cases we can just provide the necessary type arguments.
In adding type inference to the language we’ve tried to strike a balance between inference power and complexity. We want to ensure that when the compiler infers types, those types are never surprising. We’ve tried to be careful to err on the side of failing to infer a type rather than on the side of inferring the wrong type. We probably have not gotten it entirely right, and we may continue to refine it in future releases. The effect will be that more programs can be written without explicit type arguments. Programs that don’t need type arguments today won’t need them tomorrow either.
In other words, type inference may improve over time, but you should expect it to be limited.
In this case:
// This works.
func grow(array []*Foo) []*Foo {
return Prepend(array, &Foo{x: len(array)})
}
It is relatively simple for the compiler to match that the argument types of []*Foo and *Foo match the pattern []T and ...T by substitutingT = *Foo.
So why does the plain solution you gave first not work?
// Why does this not work?
func grow(array []Base) []Base {
return Prepend(array, &Foo{x: len(array)})
}
To make []Base and *Foo match the pattern []T and ...T, just substituting T = *Foo or T = Base provides no apparent match. You have to apply the rule that *Foo is assignable to the type Base to see that T = Base works. Apparently the inference system doesn't go the extra mile to try to figure that out, so it fails here.

Syntax for using a generic type as struct field [duplicate]

This question already has answers here:
Go error: cannot use generic type without instantiation
(2 answers)
Closed 5 months ago.
I am trying to define a table type in Golang using structs. This is what I have at the moment.
type Column[T any] struct {
Name string
Values map[int]T
}
I would like to use this column type to define a table like so,
type Table struct {
//Following line returns an error
Columns map[string]Column
Go's compiler is throwing an error that I need to instantiate the generic type Column.
Can anyone help me with the syntax for creating it?
You need to propagate the type from top level structure:
type Table[T any] struct {
Columns map[string]Column[T]
}
See playground
So what you're trying to do do is essentially have a number of columns, with values in different types, and collect them all in a Table type. Using plain old generics, you can do something like Ado Ren suggested:
type Column[T any] struct {
Name string
Values map[int]T
}
type Table[T any] struct {
Columns map[string]Column[T]
}
However, as I suspected, you want your Table to be able to contain multiple columns, of different types, so something like this would not work:
sCol := Column[string]{
Name: "strings",
}
uiCol := Column[uint64]{
Name: "Uints",
}
tbl := Table[any]{
Columns: map[string]any{
sCol.Name: sCol,
uiCol.Name: uiCol,
},
}
The reason, if you look at what this implies, is that the type for tbl doesn't make sense, compared to the values you're assigning. The any (or interface{} type means that what tbl is initialised to - and what it expects - is this (I replaced the Column type with an anonymous struct so the type mismatch is more obvious):
Table[any]{
Columns: map[string]struct{
Name string
Values map[int]any // or interface{}
}{
"Strings": {
Name: "Strings",
Values: map[int]string{}, // does not match map[int]any
},
"Uints": {
Name: "Uints",
Values: map[int]uint64{}, // again, not a map[int]any
},
}
}
This is essentially what is going on. Again, and as I mentioned in a comment earlier, there's a reason for this. The whole point of generics is that they allow you to create types and write meaningful functions that can handle all types governed by the constraint. With a generic Table type, should it accept differently typed Column values, that no longer applies:
func (t Table[T any]) ProcessVals(callback func(v T) T) {
for _, col := range t.Columns {
for k, v := range col.Values {
col.Values[k] = callback(v)
}
}
}
With a type like Table[uint64], you could do stuff like:
tbl.ProcessVals(func(v uint64) uint64 {
if v%2 == 1 {
return v*3 + 1
}
return v/2
})
But if some of the columns in table are strings, then naturally, this callback makes no sense. You'd need to do stuff like:
tbl.ProcessVals(func (v interface{}) interface{} {
switch tv := t.(type) {
case uint64:
case int64:
case int:
case uint:
default:
return v
}
})
Use type switches/type assertions to make sense of each value, and then process it accordingly. That's not really generic... it's basically what we did prior to the introduction of go generics, so why bother? At best it makes no difference, at worst you'll end up with a lot of tedious code all over the place, as callbacks are going to be written by the caller, and their code will end up a right mess.
So what to do?
Honestly, this question has a bit of the X-Y problem feel about it. I don't know what you're trying to do exactly, so odds are you're just going about it in the sub-optimal way. I've been vocal in the past about not being the biggest fan of generics. I use them, but sparingly. More often than not, I've found (in C++ templates especially), people resort to generics not because it makes sense, but because they can. Be that as it may, in this particular case, you can incorporate columns of multiple types in your Table type using one of golangs more underappreciated features: duck-type interfaces:
type Column[T any] struct {
Name string
Values map[int]T
}
type Col interface {
Get() Column[any]
}
type Table struct {
Columns map[string]Col
}
func (c Column[T]) Get() Column[any] {
r := Column[any]{
Name: c.Name,
Values: make(map[int]any, len(c.Values)),
}
for k, v := range c.Values {
r.Values[k] = v
}
return r
}
Now Table doesn't expect a Column of any particular type, but rather any value that implements the Col interface (an interface with a single method returning a value of Column[any]). This effectively erases the underlying types as far as the Table is concerned, but at least you're able to cram in whatever Column you like.
Demo
Constraints
If relying on a simple interface like this isn't to your taste (which, in some cases, is not desirable), you could opt for a more controlled approach and use a type constraint, in conjunction with the above Get method:
type OrderedCols interface {
Column[uint64] | Column[int64] | Column[string] // and so on
}
type AllCols interface {
// the OrderCols constraint, or any type
Column[any] | OrderedCols
}
With these constraints in place, we can make our Table type a bit safer to use in the sense that we can ensure the Table is guaranteed to only contain actual Column[T] values, not something else that just happens to match the interface. Mind you, we still have some types to erase:
type Table[T AllCols] struct {
Columns map[string]T
}
tbl := Table[Column[any]]{
Columns: map[string]Column[any]]{
c1.Name: c1.Get(), // turn in to Column[any]
c2.Name: c2.Get(),
},
}
Demo here

How to test if a generic type can fit a more restrictive interface - golang

I am building an all-purpose data structure, that I intend to use in various contexts and with various bits of data.
I am currently attempting to make a matcher, that will look into my structure and return all nodes containing the data given. My problem being that, since I need my structure to be as generic as possible, I need my data to be of a generic type matching any, and this won't allow me to make equalities.
I have built a "descendant type" (there's probably a correct term, I'm self-taught on this) that has the more rigorous comparable constraint.
I want to see if I can convert from the more general one to the more specific one (even if I have to catch an error and return it to the user). I know that I don't specifically need to, but it makes the code understandable down the line if i do it like that.
Here's a bit of code to explain my question :
type DataStructureGeneralCase[T any] struct {
/*
my data structure, which is too long to make for a good example, so I'm using a slice instead
*/
data []T
}
type DataStructureSpecific[T comparable] DataStructureGeneralCase[T]
// this works because any contains comparable
func (ds *DataStructureSpecific[T]) GetMatching(content T) int {
/*The real function returns my custom Node type, but let's be brief*/
for idx, item := range ds.data {
if item == content {
return idx
}
}
return -1
}
func (dg *DataStructureGeneralCase[T]) TryMatching(content T) (int, error) {
if ds, ok := (*dg).(DataStructureGeneral); ok {
// Does not work because dg is not interface, which I understand
} else {
return -1, fmt.Errorf("Could not convert because of non-comparable content")
}
}
My question can be summarized to "How can I do my conversion ?".
Cast to the empty interface first:
castedDg := interface{}(dg)
if ds, ok := castedDg.(DataStructureGeneralCase[T]); ok {
They explain why they chose this approach in https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#why-not-permit-type-assertions-on-values-whose-type-is-a-type-parameter

When is the tilde not necessary in Go generics?

With Golangs new generics we have the tilde operator ~ which will match the underlying type. In what case is it valid to NOT match the underlying type? I'm trying to understand why the current behavior with the tilde is not the default behavior. It seems unnecessary to support both.
For example, why would you write
interface { int }
and not
interface { ~int }
What benefit to you would it be to write a method that is so strict that it could not accept something like
type MyInt int
Why is the tilde behavior not the default, and thus the language would not require another operator?
Not using the ~ operator means you only accept the listed exact types. Why should this matter?
You may want to use the values of the exact types to set to other variables and else type conversion would be required. And because the saying goes "new type, new method set". New types having the same underlying type have their own method sets.
You may want the "original" behavior of the value, which may change if it has a different method set.
For example, let's say you want to print the number like this:
type Num interface{ ~int }
func foo[T Num](v T) {
fmt.Println(v)
}
If MyInt has a String() string method:
type MyInt int
func (m MyInt) String() string { return "bar" }
The output might not be what foo() would want, because the fmt package checks if a printed value has a String() string method, and if so, it is called to acquire its string representation:
foo(1)
foo(MyInt(1))
This will output (try it on the Go Playground):
1
bar
If you only allow int:
type Num interface{ int }
You can still call foo() with a value of type MyInt, using a type conversion:
foo(1)
x := MyInt(1)
foo(int(x))
And output will be what foo() wants, not what MyInt would want (try this one on the Go Playground):
1
1
Yes, this would also be possible if foo() itself would do the conversion, but this clearly documents you want a pure int, with int's behavior, and not something that is an int with a different, custom behavior.
Why is the tilde behavior not the default
Because it would be confusing and semantically unsound to write a function like func Foo[T int](v T) that accepts type parameters that are not int. Then the meaning of int in interface constraints would not be the same as everywhere else. (More on this discussion)
What benefit to you would it be to write a method that is so strict [...]
Indeed if the constraint includes only one exact type, using type parameters is moot. If the type set of the constraint has cardinality 1, you should just remove the type parameter.
A function like:
func Foo[T int](v T)
can only ever be instantiated with exactly int, so it can (and should!) be simply written with regular arguments:
func Foo(v int)
When the type set cardinality is N, which includes single tilde types, but also unions, makes it basically impossible to write exhaustive type switch, since using ~ in case statements is not allowed (yet?):
func Foo[T ~int | ~string](v T) {
switch t := any(v).(type) {
case int: // ok
case string: // ok
// how to match other possible types then?
}
}
In this particular case, an exhaustive type switch can be written only if the constraint includes exact types:
func Foo[T int | string](v T) {
switch t := any(v).(type) {
case int: // ok
case string: // ok
default:
panic("should not occur")
}
}
This should not arise frequently in practice: if you find yourself switching on the type parameter, you should ask yourself if the function really needs to be generic. However the use case is relevant when designing your code.
Why is the tilde behavior not the default, and thus the language would not require another operator?
Because if the approximation would be the default unconditionally you could not express the fact that your polymorphic function requires an int and not a MyInt. You would then have to introduce an operator like strict and write %int. Nothing gained.

Can I get a variable of a type based on reflect.Type [duplicate]

I have a function which takes an interface, like this:
func method(data interface{})
.. because I need to process different structs which have common fields/methods. In this function I use data tens or hundreds of times, in different places. It's really unpleasant to add switch a.(type) { case .. case .. all the time.
Is there a way to create a variable with just one switch with needed type and then just use this variable everywhere later? Something like:
var a .... // something here
switch data.(type) {
case *Struct1:
a = data.(*Struct1)
case *Struct2:
a = data.(*Struct2)
}
// Continue with 'a' only
a.Param = 15
fmt.Println(a.String())
Go is a statically typed language, the type of a must be known at compile time. And since Go does not support generics yet, you can't do what you want.
Try to come up with some other solution, e.g. abstract away the things you want to do with a into an interface, and have the concrete types implement that interface. Then a can be a variable of this interface type, and you can call methods of it.
If you can achieve this, actually you can even change the parameter of the data type to this interface, and no type assertion or type switch is needed.
Alternatively you could use reflection to access common fields (either for get or set) identified by their name, but reflection provides no compile-time guarantee, and it's usually less efficient. For an example how to do that, see this question: Assert interface to its type
You can't do what you ask for in your question directly, go is statically typed, so you can't have one variable that can hold different types, and still access that variable as if it is typed.
If you're only working on the common struct fields in your method, you are perhaps better off gathering all the common variables in its own struct, illustrated below as the commons struct and have your method take that type as an argument
package main
import (
"fmt"
)
type commons struct {
name string
age int
}
type structA struct {
commons
other_stuff int
}
type structB struct {
commons
foo string
}
func method(c* commons) {
fmt.Println(c)
c.age +=1
}
func main() {
a := structA{commons{"foo", 44}, 1}
b := structB{commons{"bar", 33}, "test"}
method(&a.commons)
method(&b.commons)
fmt.Println(a)
}
Go playground
I can't figure out what is your real goal but if the "method" you want to write handles common fields from similar structures, and you cannot fix original structures using Type Embedding, as #nos said above, then you can try to make another structure for method-internal use:
var v Vehicle // with common fields
switch data.(type) {
case *Car:
v.Handle = data.(*Car).Handle // or CircleHandle
case *Motorcycle:
v.Handle = data.(*Motorcycle).Handle // or BarHandle
}
v.Degree = 15
v.Speed = 50
v.Direction = "left"
v.Style = "rough"
/// so many things on `v`...
steering(v)
I think it is not a good approach but sometimes... :-)

Resources