Search code examples
dictionarygostructkeyunique

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


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 ?
}

Solution

  • 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.