-2

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.

    func keys(m map[?]?) []? {
       var s []?
       k := range m {
          s = append(s, k)
       }
       return s
    }
jcquokka88
  • 55
  • 6

3 Answers3

5

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
}
Eli Bendersky
  • 263,248
  • 89
  • 350
  • 412
1

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.

  • 1
    Nice, I read the first sentence and was thinking to myself "reflect doesn't solve the problem of expressing the return type!" Solution: pretend to be C and take a pointer to the destination. – hobbs Dec 01 '20 at 03:33
  • 1
    @hobbs It is common to use a pointer to the destination when the destination type is arbitrary. Some examples in the standard library are [Rows.Scan](https://godoc.org/database/sql#Rows.Scan), [json.Unmarshal](https://godoc.org/encoding/json#Unmarshal) and the other decoders in the `encoding/*` packages. –  Dec 01 '20 at 05:07
  • Yeah, it just hadn't occurred to me that they were doing it for that reason, rather than to avoid allocations (something else the stdlib loves). – hobbs Dec 02 '20 at 06:26
  • Thanks, yes this is exactly what I was looking for. – jcquokka88 Jan 30 '21 at 21:20
  • Thanks for adding the generics example. When I saw that generics were added in 1.18 I realised this is exactly what I needed - I'm so glad this is now part of the language. – jcquokka88 May 16 '22 at 07:19
0

So now that we have generics (hooray!), and as others have suggested above, here is the way I see the solution to the example in my question (and it seems to work as expected):

func keys[T comparable, V any](m map[T]V) []T {
    var s []T
    for k := range m {
        s = append(s, k)
    }
    return s
}
jcquokka88
  • 55
  • 6