In Ruby 2, keyword arguments are essentially the same as a hash object at the end of the positional arguments.
Ruby 2.x:
def test(*args)
p args
end
test(1, {y: 2}) # [1, {:y=>2}]
test(1, y: 2) # [1, {:y=>2}]
Ruby 2.x:
def test2(hash={}, **kwargs)
p [hash, kwargs]
end
test2({y: 2}) # [{}, {:y=>2}]
test2(y: 2) # [{}, {:y=>2}]
Though I have passed in a hash, it's actually picked up by the kwargs
.
This... is probably not something one would expect, right?
So, in Ruby 3, keyword arguments are completely separated from the normal arguments (learn more here).
Ruby 3.0.0:
def test2(hash={}, **kwargs)
p [hash, kwargs]
end
test2({y: 2}) # [{:y=>2}, {}]
test2(y: 2) # [{}, {:y=>2}]
If a method has explicit keyword arguments in Ruby 3, all the callers must explicitly pass in keyword arguments (or explicitly unpack a hash as the keyword args) in order to supply the keyword args.
Using this standard, one could write Ruby code that's compatible with both Ruby 2 and Ruby 3.
Caveat
There’s a catch in writing Ruby 2 and Ruby 3 compatible code though:
Ruby 2.x:
def foo; end
a = []
b = {}
foo(*a, **b) # this is okay
send(:foo, *a, **b) # this is not okay: ArgumentError (wrong number of arguments (given 1, expected 0))
There is a bug in Ruby 2 that is causing issues when unpacking empty hashes for kwargs (fixed in Ruby 3). In some cases, we’d have to conditionally unpack the kw hashes in Ruby 2 so that the code works with both Ruby versions (unpacking empty kw hashes causes errors sometimes). As a result, the actual code would look a bit ugly (with an if statement to unpack only if the kw hash is not empty), unfortunately 😅.
Another option is to use https://github.com/ruby/ruby2_keywords but it's better to actually fix the keyword argument passing when possible.
Top comments (2)
Do you have an example of "explicitly unpack a hash as the keyword args"? Also, do you have a link or documentation about the explicit keyword required in all callers? I think I am running into the issue, but I haven't been able to find much else about this.
In method calls, double splat unpacks the argument from Hash.
So
method_call(**hash)
will unpack hash arguments.Source: juanitofatas.com/ruby-3-keyword-ar...