Skip to content

Instantly share code, notes, and snippets.

@hynkle
Last active December 14, 2015 19:09
Show Gist options
  • Save hynkle/5135009 to your computer and use it in GitHub Desktop.
Save hynkle/5135009 to your computer and use it in GitHub Desktop.

Background:

  • 'frozen' in Ruby means 'immutable'.
  • Strings are not frozen by default. I.e., 'foo'.frozen? is false.

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?

Bonus silliness:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment