Home > OS >  How to set multiple types for generic values in Go correctly?
How to set multiple types for generic values in Go correctly?

Time:07-02

I want to set multiple types for values of hashmap in Golang. I implemented golang generics any, and wrote function that returned map[string]any.

However, after I ran the code, it returned

$ cannot use r.Method (variable of type string) as type T in map literal

What is the correct way of setting multiple types for values of hashmap in Go?

Here's my code

package main

type RequestProperty struct {
    Method string
    Params []any
    Id     int
}

func SetRequestProperty[T any](payloadCombine bool) map[string]T {
    var p map[string]T
    var r = RequestProperty{
        Method: "SET_PROPERTY",
        Params: []any{"combined", payloadCombine},
        Id:     5,
    }
    // just for test
    p = map[string]T{
        "method": r.Method,  // << Error Here
    }

    return p
}

func main() {
    p := SetRequestProperty(true)
}

[EDIT] This seems to be working though... I don't know why.

package main

type RequestProperty struct {
    Method string
    Params []any
    Id     int
}

// delete [T any], map[string]T 
// change it to map[string]any
func SetRequestProperty(payloadCombine bool) map[string]any {
    var p map[string]any
    var r = RequestProperty{
        Method: "SET_PROPERTY",
        Params: []any{"combined", payloadCombine},
        Id:     5,
    }
    // just for test
    p = map[string]any{
        "method": r.Method,
    }

    return p
}

func main() {
    p := SetRequestProperty(true)
}

Shouldn't T just act like an alias to type any? Am I misunderstanding something?

CodePudding user response:

Shouldn't T just act like an alias to type any?

No, it shouldn't.

T is a type parameter, not any. It is only constrained by any.

More generally: a type parameter is not its constraint.

Each time a generic function is instantiated, T is assigned a concrete type argument — which satisfies its constraint — and within the function body, map[string]T becomes a map from string to whatever the concrete T is.

p := SetRequestProperty[int](true)
// makes body look like: `var p map[string]int`
// the literal `map[string]int{"method": r.Method}` obviously can't work

Therefore at compile time, the compiler will reject assignments to T that are not compatible with all types in T's type set. The code map[string]T{"method": r.Method} doesn't compile because:

  • T is constrained by any, so its type set comprises anything
  • r.Method is of type string, and string isn't assignable to anything.

With map[string]any instead any is not used as a constraint, it is used as a static type, which is an alias of interface{} and all types are always assignable to the empty interface.

If you want to have a container with different runtime types, using any as a static type as in map[string]any is the only way. To restrict allowed types, use basic interfaces instead of type parameters.

See also the top-voted answer here: What are the benefits of replacing an interface argument with a type parameter?

  • Related