55

Is it possible to use slices as keys?

There is my attempt:

h := map[[]string]string{
  []string{"a", "b"} : "ab",
}

the compiler gives me an error invalid map key type []string. So either it's not possible or I declared it incorrectly (if so, what would be a correct way?).

ave
  • 18,083
  • 9
  • 30
  • 39

5 Answers5

75

However, it is possible to use arrays as map keys:

package main

import "fmt"

func main() {
    m := make(map[[2]int]bool)
    m[[2]int{1, 2}] = false
    fmt.Printf("%v", m)
}
mraron
  • 2,411
  • 18
  • 27
Bill DeRose
  • 2,330
  • 3
  • 25
  • 36
  • 9
    Also while while a `string` can be used a as key e.g. `map[string]bool` - a byte-slice _cannot_ `map[[]byte]bool`. But a `string` and `[]byte` can be interchangeably converted - so this works: `bs := []byte{0xde, 0xad, 0xbe, 0xef} ; mymap[string(bs)] = true` – colm.anseo Aug 02 '19 at 18:46
45

No, slices cannot be used as map keys as they have no equality defined.

hrunting
  • 3,857
  • 25
  • 23
Volker
  • 40,468
  • 7
  • 81
  • 87
22

Volker already told that this is not possible and I will give a little bit more details of why is it so with examples from the spec.


Map spec tells you:

The comparison operators == and != must be fully defined for operands of the key type; thus the key type must not be a function, map, or slice.

It already tells you that the slice can't be a key, but you could have checked it also in the comparison spec:

Slice, map, and function values are not comparable.


This means that also slice can't be a key, an array can be a key. For example you can write:

h := map[[2]string]string{
  [2]string{"a", "b"} : "ab",
}
Salvador Dali
  • 214,103
  • 147
  • 703
  • 753
6

Depending on your requirements and the complexity of your data, you could use a string as a map key and then use a hash of your slice as the map key.

The nice thing is you can use this technique with anything that can be converted to or from a slice of bytes.

Here's a quick way to convert your slice of strings into a slice of bytes:

[]byte(strings.Join([]string{},""))

Here's an example using SHA1:

type ByteSliceMap struct {
    buf *bytes.Buffer
    m   map[string][]byte
}

func (b *ByteSliceMap) key(buf []byte) string {
    h := sha1.New()
    h.Write(buf)
    sum := h.Sum(nil)
    return fmt.Sprintf("%x", sum)
}

func (t *ByteSliceMap) value(key []byte) (value []byte, ok bool) {
    value, ok = t.m[t.key(key)]
    return
}


func (t *ByteSliceMap) add(key, value []byte) {
    if t.m == nil {
        t.m = make(map[string][]byte)
    }
    t.m[t.key(key)] = value
}

Working version

Carl
  • 43,122
  • 10
  • 80
  • 104
  • 1
    This is a poor choice. Even checking to see if a key exists requires load on the GC as multiple objects have to be created in order to do the lookup. – Drew O'Meara Aug 15 '18 at 02:02
  • 4
    @DrewO'Meara do you have a suggestion or alternative? – Sebastian Jan 08 '19 at 04:29
  • 1
    This answer combines two techniques: Building short byte slice keys from long byte slices using hashing **and** converting a byte slice to a string. To convert a byte slice to a string `string(myByteSlice)` is sufficient. The conversion performs dynamic allocation (-> GC). The hashing part can be refactored in such a way that no allocation is performed (by using arrays and using e.g. Segment's fasthash). – Seoester Mar 28 '19 at 19:05
  • Another problem is different string slices produce the same key. `strings.Join([]string{"a", "b")) == strings.Join([]string{"ab"))` – Andrew W. Phillips Jan 12 '23 at 10:15
3

One way to get around this problem is to actually create a key from a slice which has well defined comparison operators:

func createKey(s []string) string { return fmt.Sprintf("%q", s) }

m := make(map[string]string)
s := []string{"a","b"}
m[createKey(s)] = "myValue"

In a similar fashion you would have to create functions for creating keys of slices with type different to string.