Why does double splat work only with symbol keys?


The double splat operator ** only seems to work with hashes whose keys are symbols. It will not work when a key is a string, for example. This is true for both ways; for construction:

def foo **; end
foo(:a => 3) #=> nil
foo("a" => 3) #=> ArgumentError: wrong number of arguments (1 for 0)

and destruction:

def bar *; end
bar(**{:a => 3}) #=> nil
bar(**{"a" => 3}) #=> TypeError: wrong argument type String (expected Symbol)

Why is it limited to symbol keys?

It may be related to the fact that named keyword notation a: 3 coincides with the syntax sugar for hash with symbol keys, but as seen above, the double splat works with the ordinary hash notation :a => 3, so I am not sure about this.

The short answer: that's how keyword arguments, which the double-splat operator is supposed to capture, are expressed in Ruby.

The long answer: the double-splat operator is for capturing keywords as seen in this answer: https://stackoverflow.com/a/18289218/3644699

Since Ruby 2.0, Ruby supports true keyword arguments. My guess is that in the main implementation they are still represented as Hashes whose keys are symbols, similar to how they were simulated before the language officually supported them.

The particular errors you're getting likely depend on implementation. For example, executing the latter code snippet in irb shows the function that raises the TypeError:

2.1.2 :001 > def bar *; end
 => :bar
2.1.2 :002 > bar(**{"a" => 3})
TypeError: wrong argument type String (expected Symbol)
    from (irb):2:in `core#hash_merge_kwd'
    from (irb):2
    from /home/mkis/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `<main>'
2.1.2 :003 >

hash_merge_kwd is an internal function, defined here: https://github.com/ruby/ruby/blob/d738e3e15533e0f500789faaedcef9ed9ca362b9/vm.c#L2398