Using object as key for dictionary and mutating it yields different results when getting value for this key

Hello. I'm running following code and output is a bit surprising for me:

class Foo: Hashable {
    var bar: String

    init(bar: String) {
        self.bar = bar
    }

    static func == (lhs: Foo, rhs: Foo) -> Bool {
        return lhs.bar == rhs.bar
    }

    func hash(into hasher: inout Hasher) {
        hasher.combine(bar)
    }
}

let value = 123

let foo = Foo(bar: "bar")
var dictionary = [Foo: Int]()
dictionary[foo] = value
foo.bar = "baz"
if dictionary[foo] == value {
    print("wat")
} else {
    print("WAT")
}

Sometimes it outputs "wat" and sometimes "WAT". I looked into Dictionary sources to try and understand why the output may be different and found that it may be something with resizing and re-creating hashtable but I'd like to get some feedback from someone who has more experience with Dictionary implementation details.

My questions are:

  • is this the expected behavior?
  • if yes then why?
  • if no then what is the expected behavior?

I think this is expected behaviour: you're changing the hash of a key after it has been used in the dictionary (and I suppose the implementation buckets them or something similar).
If you want to mutate a key you have to make sure that what you mutate doesn't affect its hashValue, or you can use a derived key instead (e.g. ObjectIdentifier)

If that's the case I'd suppose to consistently not get the value for mutated key. But in this example it doesn't happen consistently.

Hashing details (the seed) change for each run, so something that depends on the exact hashing function and not only on its semantics can change between runs. (e.g. which different values clash and which don't)