'How to write a function on a map with unknown type?

I read with interest a question about how to create a slice of the keys of a map in Go (using a range loop), but how would you write a function that would do this for a map of any type? The map value type is a particular nuisance since it doesn’t even affect the type of the resulting slice.

For instance, could I write a function that takes as argument a map[int]T (where T is any type) and returns a []int (integer slice) of the map’s keys? And if that’s possible, can a function be made to operate on a map[T1]T2 (where T1 and T2 are any type) and return a []T1 slice? That is, what do I replace the question marks with in the below code? I’m guessing I need an interface but I’m a bit of a beginner with those.

    function keys(m map[?]?) []? {
       var s []?
       k := range m {
          s = append(s, k)
       }
       return s
    }


Solution 1:[1]

You could do it for types like map[int]interface{}.

func keys(m map[int]interface{}) []int {
   var s []int
   for k := range m {
      s = append(s, k)
   }
   return s
}

To learn about interfaces, start with the Tour of Go: https://tour.golang.org/methods/9


But this is not possible with concrete types before Go gets generics.

Generics are on the way, though. Once they land in Go, you'll be able to write:

func keys[T any](m map[int]T) []int {
    var s []int
    for k := range m {
        s = append(s, k)
    }
    return s
}

Solution 2:[2]

Use generics in Go 1.18 or later:

// Keys returns the keys from in map m.
func keys[M ~map[K]V, K comparable, V any](m M) []K {
    result := make([]K, 0, len(m))
    for k := range m {
        result = append(result, k)
    }
    return result
}

Use it like this:

r := keys(map[string]string{"Foo": "Bar", "Quux": "Baz"})
// r is a []string

Use the reflect package to extract the keys from an arbitrary map type in Go versions before 1.18:

// Keys stores the keys from m to the slice pointed to by pkeys.
// The slice element type must be the same as the map key type.
func keys(m interface{}, pkeys interface{}) {
    result := reflect.ValueOf(pkeys).Elem()
    result.SetLen(0)
    result.Set(reflect.Append(result, reflect.ValueOf(m).MapKeys()...))
}

Use it like this:

var result []string
keys(map[string]string{"Foo": "Bar", "Quux": "Baz"}, &result)
fmt.Println(result) // prints [Foo Quux]

Here's how to write the function for a specific key type. In this example, the key type is int:

// Keys returns the keys for for map m with int keys.
func keys(m interface{}) []int {
    mv := reflect.ValueOf(m)
    result := make([]int, mv.Len())
    for i, key := range mv.MapKeys() {
        result[i] = key.Interface().(int)
    }
    return result
}

Use it like this:

result := keys(map[int]string{1: "Bar", 2: "Baz"})
fmt.Println(result)  // prints [1 2]

See all of the examples in action on the Go playground.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2