Why Keyword Arguments Had To Change in Ruby 2.7.0

September 04, 2020

Note: This article is all about behavior of Keyword Arguments from Ruby 2.0 to 2.6.6 (latest version of 2.6 release).

Keyword Arguments were introduced in Ruby language with version 2.0 and it has been used extensively across many projects because of the clarity it brings to code by giving a name to the argument.

When we use keyword arguments, error message says which argument is missing while invoking a method.

def result(batch, name:, grade:) puts "Result for batch #{batch}"\ "- Hi #{name}, your grade is #{grade}" end student = { name: "John" } result('2009', student) #`result': missing keyword: grade (ArgumentError)

Above, we are missing the keyword argument grade and error message clearly speaks about it and reduces some effort while debugging a project.

We can see above, we have passed a student argument which is a Hash object to result(). Ruby is smart enough to automatically consider it as keyword argument. This automatic conversion of Hash object to keyword argument works only if it is the last argument.

Every good thing comes with a price ...

While keyword arguments were a fresh air to breath in Ruby language, it also added some unexpected behavior.

Let's see this example:

def result(student, batch: '2020', name: 'Mike', grade: 5) puts "Result for batch #{student[:batch]}"\ "- Hi #{student[:name]}, your grade is #{student[:grade]}" puts batch, name, grade end student = { batch: '2012', name: "John", grade: 9 } result(student) #=> Result for batch 2012- Hi John, your grade is 9 #=> 2020 #=> Mike #=> 5

In the above example, student is called positional argument and batch, name, grade are called keyword arguments.

After the initial release of Ruby 2.0, the above used to throw ArgumentError: wrong number of arguments (0 for 1).
As we just said above, Ruby automatically converts last Hash object argument to keyword argument and adhering to that principle it throws this error rightfully.

This felt weird!

We are passing a Hash object argument to student and it should just work for us.

Ruby core team identified this problem and added a fix by giving priority to positional arguments over keyword arguments in this scenario.
This change was then added to earlier releases as well.

Real frustration!!

However, priority is given to positional argument only when it does not hold any value.

Let's see an example where positional argument has a default value (mark the change student={}):

def result(student={}, batch: '2020', name: 'Mike', grade: 5) puts "Result for batch #{student[:batch]}"\ "- Hi #{student[:name]}, your grade is #{student[:name]}" puts batch, name, grade end #=> Result for batch - Hi , your grade is #=> 2012 #=> John #=> 9

Let's closely examine the output:

#expected Result for batch 2012- Hi John, your grade is 9 #actual result Result for batch - Hi , your grade is

We can see above we are not getting the excepted result.

A necessary change to differentiate between positional argument and keyword argument was long due.

Finally, there was a proposal accepted to introduce real keywords in Ruby to have a clear separation between positional argument and keyword arguments.

Ruby 2.7.0 deprecated use of last Hash object as keyword argument and we will be warned if we continue to use it.

And, this has been removed from Ruby 3.0 which is going to be released this December, 2020 tentatively.

In an another post, we will see what are the new changes in keyword arguments from Ruby 2.7.0 onwards and how should we use it.

Thanks for your time!

Share feedback with us at:


© All rights reserved 2022