Mutable object is an object that could be changed ‘in place’. Immutable, on the other hand means that you need to create another object if you want to change the value. Like this:
One of the interesting consequences of mutability is a mutable object’s behavior when used as hash key.
To understand what happened here we need to clarify how hashes store and retrieve objects. First let’s disambiguate the word hash. In Ruby Hash
is a class representing a data structure called dictionary or hash map. The word hash in hash map implies that when we add a key-value pair to a dictionary, key’s hash is stored along with the original pair. And when we search for a given key later, it’s found by first comparing key’s hashes and then, if hash was found, by keys themselves.
So when we used object a
as a key to the hash h
, a.hash
was called. Object.hash
is a method returning a hash of a given object (Department of Redundancy Department called - they want their sentence back).
Then we mutated object a
by adding a new element to it. Let’s check what happened with a
’s hash.
Predictably the hash of a
has changed. But the old hash of a
is already saved in h
! And when we do h[a]
interpreter compares a.hash
with what is stored in h
and doesn’t find a match. This is also the reason why the hash h
can have two seemingly similar keys - {[1, 2, 3] => true, [1, 2, 3] => false}
.
But if the hash in h
is the hash of [1, 2]
then it should be possible to retrieve a value under a = [1, 2, 3]
by the key [1, 2]
.
This expression returned nil
because interpreter compares not only hashes but also keys. When we try to get a value using mutated object as a key, Ruby fails to find matching hash. When we use [1, 2]
- it fails to find matching key.
So to get true
from h
we need an object with hash like of array’s [1, 2]
and has a value like array [1, 2, 3]
. To get that object we can rewrite hash
method.
And that’s why it’s bad to use mutable objects as hash keys.