Hash
Hash(object) Synopsis
Kernel#Hash is a module method on Kernel, mixed into every Object because Kernel is included at the top of the class chain. It accepts exactly one argument and coerces that argument into a Hash using a small, strict rule set. The rule set is narrow on purpose; Kernel#Hash is not a fallback for every hash-shaped value.
Hash(object) # => Hash
Behavior
The method walks four checks in order and stops at the first one that matches:
- If
objectis already aHash, return the same object reference. No copy is made, so identity is preserved and later mutation of the result affects the original. - If
objectisnilor an empty array[], return a new emptyHash{}. This branch is hard-coded in the C implementation and applies only to those two values, not to every empty or falsy input. - If
object.respond_to?(:to_hash)andto_hashreturns aHash, return that hash.to_hashis Ruby’s strict protocol for hash-like objects. - Otherwise, raise
TypeErrorwith a message saying the argument cannot be converted into a Hash.
Most people are caught off guard by how strict the third rule is. An object whose to_hash returns a non-Hash value is treated as a failure, the same outcome as having no to_hash method at all, and that differs from the looser to_h protocol used by Hash#to_h. Returning a non-Hash from to_hash puts you in a worse spot than not implementing it at all.
The first rule is strict in a different way. There is no copy, no freeze, no defensive duplication. The result is identical to the input by equal?. If you want a fresh copy, ask for one explicitly.
All four branches in one snippet:
h = {a: 1}
Hash(h).equal?(h) # => true # rule 1: same object reference
Hash(nil) # => {} # rule 2: nil special case
Hash([]) # => {} # rule 2: empty array special case
Hash(42) # raises TypeError # rule 4: no conversion path
Rule 1 is verified with equal? on a single stored reference. Rules 2 covers both the nil and empty-array special cases. Rule 4 is the catch-all. Rule 3 (a working to_hash) is rare in practice; the OpenStruct example below is the more common case where it does not exist.
Examples
The basic conversions, taken from the official docs:
Hash({foo: 0, bar: 1}) # => {:foo=>0, :bar=>1}
Hash(nil) # => {}
Hash([]) # => {}
These cover the first two rules in the behavior list. The first example shows a hash passed through unchanged. The second and third cover the nil and empty-array special case, which both return the same empty {} value, even though they come from very different inputs.
Kernel#Hash does not copy a Hash argument. It returns the same object, which you can verify with equal?:
h = {a: 1}
Hash(h).equal?(h) # => true
That means mutating the result mutates the input, which is a frequent source of bugs when developers assume they got a fresh copy. If you need a defensive copy, reach for Hash#dup, Hash.new, or Hash#merge with an empty hash.
Symbols, integers, floats, and non-empty arrays all raise TypeError. None of these define to_hash, so rule 3 never fires and rule 4 takes over:
Hash(:foo) # raises TypeError: can't convert Symbol to Hash
Hash(42) # raises TypeError
Hash([:a]) # raises TypeError
Notice the asymmetry with the earlier example. Hash([]) returned {} because of the empty-array special case, but Hash([:a]) raises. Arrays do not define to_hash at all, so the only way an array can produce a hash is through that special case.
OpenStruct defines to_h, not to_hash, so it raises here even though it has a hash-like method:
require "ostruct"
Hash(OpenStruct.new(a: 1)) # raises TypeError
This is the most common gotcha for Kernel#Hash. If you want a hash from an OpenStruct, call OpenStruct.new(a: 1).to_h instead. The same applies to Struct, ENV, and any other object that implements only the loose to_h protocol.
A Hash subclass is still a Hash, so it is returned as-is, and its subclass identity is kept on the way out:
class MyHash < Hash; end
Hash(MyHash.new) # => #<MyHash:{}>
The class is preserved, so Hash(MyHash.new).class is MyHash, not Hash. This matters when downstream code checks is_a?(Hash) against a specific subclass or relies on subclass-specific methods.
Zero arguments raises ArgumentError because rule 1 through 4 each require an argument to inspect:
Hash() # raises ArgumentError: wrong number of arguments (given 0, expected 1)
When to use
Kernel#Hash is the right tool when you want to normalize an argument to a hash and raise on anything unexpected. It sits between three other common hash-coercion paths, and the differences are worth knowing:
Hash(x)is strict, raises on failure, and special-casesniland[].x.to_his loose, works onOpenStruct,Struct,ENV, andnil.Hash[x]is a class method that builds a hash from an array of pairs.
A side-by-side on the same input:
require "ostruct"
o = OpenStruct.new(name: "Ada", age: 36)
o.to_h # => {:name=>"Ada", :age=>36} # loose protocol
Hash(o) # raises TypeError # strict protocol, no to_hash
Hash[o.to_h] # => {:name=>"Ada", :age=>36} # Hash[] copies an existing hash
OpenStruct only ships to_h, so the middle call is the one that surprises people. Use to_h when you want the loose protocol, Hash[] when you have pairs, and Kernel#Hash only when you know the input either is a Hash or implements to_hash.
For converting user input or untrusted data, Kernel#Hash is often the safest choice because it surfaces bad inputs as exceptions and never silently produces {}. For library APIs that need to be permissive, to_h is usually the better fit.
A common pattern at an API boundary is to wrap the call so a clean error type comes out:
def require_hash(value)
Hash(value)
rescue TypeError
raise ArgumentError, "expected a Hash, got #{value.class}"
end
Common gotchas
- Same object, not a copy.
Hash(some_hash)returns the same reference. Mutations leak in both directions. - Empty array is magic.
Hash([])returns{}, butHash([:a])raises. The empty-array special case does not generalize to other empty values such as""or0. to_hashversusto_h.Kernel#Hashuses the strictto_hashconversion.Hash#to_huses the looserto_hprotocol. Objects that implement onlyto_h(such asOpenStruct,Struct, andENV) raiseTypeErrorhere.- No
exception:keyword. UnlikeKernel#IntegerandKernel#Float,Kernel#Hashhas noexception: falsefallback. Conversion failure always raises. - No block form. You cannot pass a block to compute a default. For that, reach for
Hash.new(0)orHash.new { |h, k| h[k] = [] }. - No zero-argument form.
Hash()raisesArgumentError. You must pass exactly one argument. - Not a constructor.
Kernel#Hashis unrelated toHash.new, to theHash[...]class method, and to the{}literal. The only thing it does is coerce a single argument. - Hash subclasses are preserved. A
Hashsubclass instance is returned with its class intact. It is not converted to a baseHash.
A quick walkthrough of several of these in one block:
h = {a: 1}
Hash(h).equal?(h) # => true # same-object, not a copy
Hash(h)[:b] = 2 # also mutates h # leak in both directions
Hash([]) # => {} # empty-array special case
Hash([:a]) # raises TypeError # non-empty array has no to_hash
Hash() # raises ArgumentError # zero-arg form is not allowed
Line 1 is the identity check. Line 2 shows the practical consequence: writing to the result writes to the original h too. Lines 3 and 4 highlight the asymmetry between empty and non-empty arrays. Line 5 confirms that the zero-argument form is a hard error, separate from the TypeError raised by bad input.
See also
- Kernel#Array — sibling Kernel conversion that walks the
to_arythento_afallback chain. - Kernel#Integer — sibling Kernel conversion that supports the
exception:keyword, whichKernel#Hashdeliberately does not. - Hash#to_h — the looser
to_hprotocol thatKernel#Hashdoes not delegate to.