Background:
- 'frozen' in Ruby means 'immutable'.
- Strings are not frozen by default. I.e.,
'foo'.frozen?
isfalse
.
Question: {'foo' => true}.keys.first.frozen?
Answer: true
Okay. I can totally see why you don't want hash keys to be mutable. That's completely sensible.
Question: {[] => true}.keys.first.frozen?
Answer: false
Oh. Well. Okay then. Whence the inconsistency? Popping open MRI's source code (for non-rubyists: that's the canonical Ruby implementation), we find this in Hash's source code in the rb_hash_aset
function (edited for formatting):
if (tbl->type == &identhash || rb_obj_class(key) != rb_cString) {
RHASH_UPDATE_ITER(hash, iter_lev, key, hash_aset, val);
} else {
RHASH_UPDATE_ITER(hash, iter_lev, key, hash_aset_str, val);
}
Where hash_aset
is this simple function:
static int
hash_aset(st_data_t *key, st_data_t *val, st_data_t arg, int existing)
{
*val = arg;
return ST_CONTINUE;
}
And hash_aset_str
is:
static int
hash_aset_str(st_data_t *key, st_data_t *val, st_data_t arg, int existing)
{
*key = (st_data_t)rb_str_new_frozen((VALUE)*key);
return hash_aset(key, val, arg, existing);
}
which, as you can see, freezes any string keys passed in.
In Ruby's defence, this difference between string keys and non-string keys is documented:
hsh[key] = value → value
store(key, value) → value
Element Assignment—Associates the value given by value with the key given by key. key should not have its value changed while it is in use as a key (a String passed as a key will be duplicated and frozen).
…But honestly, what percent of developers are ever going to read the docs for something as basic as Hash#store
?
Question:
class Stringling < String
end
{Stringling.new('foo') => true}.keys.first.frozen?
Answer: false
lol.
MRI source code and docs taken from ruby-2.0.0p0. Behavior is the same in Ruby 1.9 (and likely older versions too, but I didn't check).