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

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

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.

Golang wrap calls to package methods generically [duplicate]

I want to write a single function that can add certain fields to Firebase message structs. There are two different types of message, Message and MulticastMessage, which both contain Android and APNS fields of the same types, but the message types don't have an explicitly declared relationship with each other.
I thought I should be able to do this:
type firebaseMessage interface {
*messaging.Message | *messaging.MulticastMessage
}
func highPriority[T firebaseMessage](message T) T {
message.Android = &messaging.AndroidConfig{...}
....
return message
}
but it gives the error message.Android undefined (type T has no field or method Android). And I can't write switch m := message.(type) either (cannot use type switch on type parameter value message (variable of type T constrained by firebaseMessage)).
I can write switch m := any(message).(type), but I'm still not sure whether that will do what I want.
I've found a few other SO questions from people confused by unions and type constraints, but I couldn't see any answers that helped explain why this doesn't work (perhaps because I'm trying to use it with structs instead of interfaces?) or what union type constraints are actually useful for.
In Go 1.18 you cannot access common fields1, nor common methods2, of type parameters. Those features don't work simply because they are not yet available in the language. As shown in the linked threads, the common solution is to specify methods to the interface constraint.
However the the types *messaging.Message and *messaging.MulticastMessage do not have common accessor methods and are declared in a library package that is outside your control.
Solution 1: type switch
This works fine if you have a small number of types in the union.
func highPriority[T firebaseMessage](message T) T {
switch m := any(message).(type) {
case *messaging.Message:
setConfig(m.Android)
case *messaging.MulticastMessage:
setConfig(m.Android)
}
return message
}
func setConfig(cfg *messaging.AndroidConfig) {
// just assuming the config is always non-nil
*cfg = &messaging.AndroidConfig{}
}
Playground: https://go.dev/play/p/9iG0eSep6Qo
Solution 2: wrapper with method
This boils down to How to add new methods to an existing type in Go? and then adding that method to the constraint. It's still less than ideal if you have many structs, but code generation may help:
type wrappedMessage interface {
*MessageWrapper | *MultiCastMessageWrapper
SetConfig(c foo.Config)
}
type MessageWrapper struct {
messaging.Message
}
func (w *MessageWrapper) SetConfig(cfg messaging.Android) {
*w.Android = cfg
}
// same for MulticastMessageWrapper
func highPriority[T wrappedMessage](message T) T {
// now you can call this common method
message.SetConfig(messaging.Android{"some-value"})
return message
}
Playground: https://go.dev/play/p/JUHp9Fu27Yt
Solution 3: reflection
If you have many structs, you're probably better off with reflection. In this case type parameters are not strictly needed but help provide additional type safety. Note that the structs and fields must be addressable for this to work.
func highPriority[T firebaseMessage](message T) T {
cfg := &messaging.Android{}
reflect.ValueOf(message).Elem().FieldByName("Android").Set(reflect.ValueOf(cfg))
return message
}
Playground: https://go.dev/play/p/3DbIADhiWdO
Notes:
How can I define a struct field in my interface as a type constraint (type T has no field or method)?
In Go generics, how to use a common method for types in a union constraint?

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

Getting a base type from interface when using generics

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.

Creating objects dynamically based on a string

I'm trying to dynamically create structs based on a string.
In the below example reflect.TypeOf &c and &c1 are different because I return interface{} from makeInstance. TypeOf c and c1 are the same.
My question is how do I change the way I handle the output of makeInstance so it creates an object identical to c1 but will still allow me to create objects identical to b1 also?
type Car struct {
Make int `json:"make"`
Model int `json:"model"`
}
type Bus struct {
Seats int `json:"seats"`
Route int `json:"route"`
}
var typeRegistry = make(map[string]reflect.Type)
func init() {
typeRegistry["Car"] = reflect.TypeOf(Car{})
typeRegistry["Bus"] = reflect.TypeOf(Bus{})
}
func makeInstance(name string) interface{} {
v := reflect.New(typeRegistry[name]).Elem()
return v.Interface()
}
func main() {
c := makeInstance("Car")
b := makeInstance("Bus")
var b1 Bus
var c1 Car
fmt.Println(reflect.TypeOf(&c))
fmt.Println(reflect.TypeOf(&c1))
fmt.Println(reflect.TypeOf(c))
fmt.Println(reflect.TypeOf(c1))
Edit:
My overall outcome is to have a program that reads a json config file that will list the types of objects I want to go off and hit a rest api and collect e.g.
{
"auth":[{
"username": "admin",
"password": "admin"
}],
"transport":[
{
"vehicle":["car", "bus"]
}]
}
When looping through vehicle it would hit an api and perform a query and return data. I would then want to create an array of the which ever vehicle is included in the config and unmarshal the json response into it. I'm trying to create the objects dynamically so I could avoid having to check if vehicle = car, if vehicle = bus etc as I will eventually have many types of vehicles but may not always have every vehicle and it seems long winded.
The function returns values of type Car and Bus as written. If you want the variable in main to have a specific type, then use a type assertion:
c := makeInstance("Car").(Car)
If your goal is to get a pointer to values of these types, then return the pointer from makeInstance:
func makeInstance(name string) interface{} {
return reflect.New(typeRegistry[name]).Interface()
}
You probably should stop and read about interface values type assertions. Go is not a dynamically typed language and what you are trying to do will fail with high probability:
As long as you are working with interface{} you simply cannot access the fields (Make, Model, Seats, Route, ...) without reflection. If you want to write x.Make you must have a x of type Car or *Car (and not interface{}).
To get from c of type interface{} to e.g. a Car you must type assert:
var car Car = c.(Car)
Note that you cannot do dynamic type assertions (without reflection) and that c.(Car) will fail if c contains e.g. a Bus. So after json.Unmarshaling into a generic interface{} you will have to switch on the known type and assert to that type. Which means you will write dedicated code for each type anyway.

Resources