Ruby Hash Default Values, Versatile (Of Course!)

Burdette Lamar - Mar 9 '20 - - Dev Community

When you refer to a non-existent key, a Ruby Hash object returns its default value, right?

Well, not so fast!

The returned value for a nonexistent key is actually controlled by not one, but two, settings:

  • Default value: a stored object.
  • Default proc: a stored Proc.

Default Value

The simplest case is seen in a new Hash that was created with no argument:

  • The default value is initially nil.
  • The default proc is initially nil.
h = Hash.new
h.default # => nil
h.default_proc # => nil
h[:nosuch] # => nil

Whenever the default proc is nil, the returned default value for the Hash comes from #default.

You can initialize the default value with method Hash.new:

h = Hash.new(0)
h.default # => 0
h[:nosuch] # => 0

You can set the the default value with method #default=:

h.default = false
h.default # => false
h[:nosuch] # => false

So far, so good. The default value has ruled. (BUT only because the default proc has been nil.)

Default Proc

When the default proc is not nil, the returned default value is determined by the default proc alone.

How? We'll get to that.

First, you can initialize the default proc by including a block with Hash.new:

h = Hash.new { |hash, key| "Default value for #{key}" }
h.default_proc.class # => Proc

Or you can set the default proc with method #default_proc=:

h = Hash.new
h.default_proc # => nil
h.default_proc = proc { |hash, key| "Default value for #{key}" }
h.default_proc.class # => Proc

When the default proc is set (i.e., not nil), a reference to a non-existent key is handled thus:

  • The proc is called with both the Hash object itself and the missing key.
  • The block's return value is returned as the key's default value:
h = Hash.new { |hash, key| "Default value for #{key}" }
h[:nosuch] # => "Default value for nosuch"

Note that in this case, the default value is ignored:

h.default = false
h[:nosuch] # => "Default value for nosuch"

Note also that in the example above no entry for key :nosuch is created:

h.include?(:nosuch) # => false

However, the block itself can add a new entry:

h = Hash.new { |hash, key| hash[key] = "Subsequent value for #{key}"; "First value for #{key}" }
h.include?(:nosuch) # => false
h[:nosuch] # => "First value for nosuch"
h.include?(:nosuch) # => true
h[:nosuch] # => "Subsequent value for nosuch"
h[:nosuch] # => "Subsequent value for nosuch"

Finally, setting the default proc back to nil causes a missing-key reference to return the default value:

h.default_proc = nil
h.default = false
h[:nosuch] # => false

That's all, Folks!

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .