The Evolution of Keyword Arguments in Ruby: From 1.9 to 3.0

Devaraju Boddu
4 min readNov 3, 2023

Ruby, known for its flexibility and developer-friendly syntax, has seen significant changes in how it handles keyword arguments over the years. Understanding this evolution is crucial for developers transitioning from older versions of Ruby to the latest release, Ruby 3.0. In this article, I’ll explore the changes in keyword arguments from Ruby 1.9 to 2.6 and finally to Ruby 3.0, with practical examples to illustrate each version’s behavior.

Ruby 1.9: Positional Arguments

Positional arguments are the most basic way to pass arguments to a method. In this approach, arguments are passed to the method in the order defined by the method’s parameter list. The values are assigned to parameters based on their position.

# Positional Args
def add(a, b)
a + b
end

result = add(3, 5)

Before Ruby 1.9, methods relied on positional arguments. Named arguments were not officially supported, but developers often used a workaround by passing a single hash as the last argument to a method. Here’s a simple example:


# Positional Args as Named args
def example_method(options = {})
name = options[:name] || 'Default'
age = options[:age] || 0
puts "#{name} is #{age} years old."
end

example_method({ name: 'John', age: 30 })

In this version of Ruby, named arguments were simulated using a hash, making code less readable and maintainable.

Ruby 2.6: Mixed Positional and Keyword Arguments

Ruby 2.1 introduced the required_keyword library. Ruby 2.6 brought further flexibility by allowing the mixing of positional and keyword arguments. This feature enabled developers to pass keyword arguments before positional arguments, making method calls more versatile. Here’s how it worked in Ruby 2.6:

# Ruby 2.6
def greet(name: "Guest", age: 25, message: "Hello")
puts "#{message}, #{name}! You are #{age} years old."
end

# Mixed positional and keyword arguments
greet("Alice", age: 30) # Hello, Alice! You are 30 years old.

def example_method(name:, age: 0)
puts "#{name} is #{age} years old."
end

example_method(name: 'John', age: 30) # Hello, John! You are 30 years old.

In Ruby 2.6, you could mix positional arguments with keyword arguments in any order, providing more freedom when calling methods. This behavior was a departure from the strict separation of positional and keyword arguments introduced in Ruby 3.0.

Ruby 3.0: Strict Separation of Positional and Keyword Arguments

Ruby 3.0 introduced a stricter separation between positional and keyword arguments. In this version, you must specify positional arguments before keyword arguments in method calls. Additionally, you can make keyword arguments mandatory by using a double colon (::) instead of a single colon. Here’s how it works in Ruby 3.0:

# Ruby 3.0
def greet(name: "Guest", age: 25, message: "Hello")
puts "#{message}, #{name}! You are #{age} years old."
end

# In Ruby 3.0, you need to pass positional arguments before keyword arguments:
greet("Alice", age: 30) # This works in Ruby 3.0

# You can make an argument mandatory with a double colon
def new_greet(name:, age::, message: "Hello")
puts "#{message}, #{name}! You are #{age} years old."
end

# In Ruby 3.0, age is mandatory, and you must provide it:
new_greet(name: "Bob", age: 35) # Hello, Bob! You are 35 years old.

Ruby 3.0 enforces a strict order for positional and keyword arguments. This change was introduced to make method calls more consistent and predictable. However, it can potentially break code that relies on the more flexible argument handling from Ruby 2.6.

Ruby 3.0 also introduced the ** operator, which enables gathering additional keyword arguments into a hash within the method:

def example_method(name:, age: 0, **options)
puts "#{name} is #{age} years old."
puts "Additional options: #{options}"
end

example_method(name: 'John', age: 30, city: 'New York', country: 'USA')

These changes in Ruby 3.0 improved the expressiveness, readability, and type safety of keyword arguments in Ruby, making it a more robust feature for developers.

Potential Errors and Issues in Ruby 3.0

When transitioning from Ruby 1.9 or 2.6 to Ruby 3.0, developers may encounter errors related to the changes in keyword arguments:

Syntax Errors

Error in Argument Order

In Ruby 1.9 and 2.6, you could mix positional and keyword arguments more freely. If you pass keyword arguments before positional arguments in Ruby 3.0, it will result in a syntax error.

# Ruby 2.6
def greet(name: "Guest", age: 25)
puts "Hello, #{name}! You are #{age} years old."
end

# Calling the method with keyword arguments before positional arguments
greet(age: 30, "Alice") # Syntax error in Ruby 3.0

In Ruby 2.6, the above code would work without issue, but it will result in a syntax error in Ruby 3.0 because it enforces a strict order for arguments.

Missing Argument Errors

Mandatory Keyword Arguments

Ruby 3.0 allows you to declare keyword arguments as mandatory using double colons (::). If you attempt to run code that relies on mandatory keyword arguments in Ruby 3.0 in Ruby 1.9 or 2.6, you will encounter a missing argument error.

# Ruby 3.0
def new_greet(name:, age::, message: "Hello")
puts "#{message}, #{name}! You are #{age} years old."
end

# Calling the method in Ruby 1.9 or 2.6
new_greet(name: "Bob") # Missing argument error in Ruby 1.9 or 2.6

In Ruby 3.0, the age the argument is mandatory (indicated by double colons), so not providing it in Ruby 1.9 or 2.6 will result in an error.

To avoid these errors when transitioning from Ruby 1.9 or 2.6 to Ruby 3.0, you’ll need to update your code to adhere to Ruby 3.0’s keyword argument rules. This may involve rearranging argument order, providing values for mandatory keyword arguments, and eliminating code that relies on the more flexible argument handling of earlier Ruby versions.

By understanding the historical context of these changes and the potential issues, you can make informed decisions when writing and maintaining Ruby code in different versions of the language.

--

--

Devaraju Boddu

Technical Consultant | Ruby On Rails | Java | Spring Boot | AWS