How can I prevent a type being used as a map key?

2019-02-18 01:33发布

问题:

I have a type that can be used as a map key, but I want to prevent this from occurring. I assumed that if the type contained a private member it wouldn't be possible from other packages, but this appears to work anyway. What's the best way to make the type unusable as a map key?

type MyType struct {
    A *A
    b b

    preventUseAsKey ?
}

回答1:

I don't see any benefit of disallowing a type being used as a key. It is just an option which may or may not be used, the type will not be any better or smaller or faster just because you forbid to use it as a map key.

But if you want to do it: Spec: Map types:

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.

So if you can violate the terms of the comparison operators, you implicitly get what you want. You have a struct, terms for the struct types:

Struct values are comparable if all their fields are comparable. Two struct values are equal if their corresponding non-blank fields are equal.

So struct values are only comparable (and thus can only be used as keys in maps) if all their fields are comparable. Simply add a field whose type is not comparable.

Slice, map, and function values are not comparable.

So for example add a field whose type is a slice, and you're done:

type MyType struct {
    S             string
    i             int
    notComparable []int
}

Attempting to use the above MyType as a key:

m := map[MyType]int{}

You get a compile-time error:

invalid map key type MyType

Note:

I wrote about not having any benefit of forbidding the type being a key. It's more than that: from now on you won't be able to use comparison operators on values of your type anymore (because of the extra, non-comparable field), so e.g. you lose the option to compare those values:

p1, p2 := MyType{}, MyType{}
fmt.Println(p1 == p2)

Compile-time error:

invalid operation: p1 == p2 (struct containing []int cannot be compared)

Note that with a little trick you could still preserve the comparable nature of your type, e.g. by not exporting your type but a wrapper type which embeds the original one; and add the extra, non-comparable type to the wrapper type, e.g.:

type myType struct {
    S string
    i int
}

type MyType struct {
    myType
    notComparable []int
}

func main() {
    p1, p2 := MyType{}, MyType{}
    fmt.Println(p1.myType == p2.myType)
}

This way your myType can remain comparable but still prevent the exported, wrapper MyType type to be used as key type.



回答2:

Your type should not be comparable in order to be unfit as a map key.

Slice, map, and function values are not comparable

See Key Type:

Notably absent from the list are slices, maps, and functions; these types cannot be compared using ==, and may not be used as map keys.

So if your type is a slice, map or function, you should get what you need.
It could be an "alias" (defining a new named type):

type StringSliceWrap []string
type MyFunc func(i int)

That alias would not be used as a map key.


Update 2017: Brad Fitzpatrick give this tip (adding a slice in your struct) to make sure your type struct is not comparable: See play.golang.org:

package main

// disallowEqual is an uncomparable type.
// If you place it first in your struct, you prevent == from
// working on your struct without growing its size. (Don't put it
// at the end; that grows the size of the struct)
type disallowEqual [0]func()

type T struct {
    _ disallowEqual
    Foo string
    Bar int
}

func main() {
    var t1 T
    var t2 T
    println(t1 == t2)
}

T cannot be used as amp key now!