1785 views
What's the return value of the following Ruby 3 code?
def foo(bar = (baz = true)) baz end foo(42) # => ???
The correct answer is
true
It raises NameError
42
nil
Subscribe to RubyCademy and get free access to all our courses, plus hundreds of fun Ruby cards, quizzes, guides, and tutorials!
In Ruby, the ability to define default argument values for method parameters is a flexible feature that can handle both simple and complex use cases.
One such advanced pattern involves setting a default value for an argument using a nested assignment within parentheses.
def foo(bar = (baz = true)) baz end foo(42) # => nil
In the given example, the method foo
has a parameter bar
with a default value defined as (baz = true)
.
This syntax accomplishes two things: it sets a default value for bar
, ensuring that if no argument is passed to foo
, bar
is assigned as true
. It also defines and initializes the local variable baz
, which is tied to the expression (baz = true)
.
When the method foo
is called with the argument 42
, the value 42
is explicitly assigned to the parameter bar
, bypassing the default value (baz = true)
.
Since the default value expression is not evaluated, the baz
variable is initialized but no value is assigned to it.
Because the default value of a variable in Ruby is nil
, the method call foo(42)
returns nil
as baz
is nil
.
If foo
is called without any arguments, the default value expression (baz = true)
is evaluated.
The parameter bar
is assigned the value true
, and the local variable baz
is initialized to true
as part of the default value assignment. The method returns the value of baz
, which is true
.
Rails employs a similar pattern in the implementation of ActiveSupport::HashWithIndifferentAccess#default
. This method extends the behavior of Ruby's Hash#default
, allowing a default value or block to handle cases where a key is accessed but does not exist.
Here's the implementation:
def default(key = (no_key = true)) if no_key super() else super(convert_key(key)) end end
The method parameter key
has a default value defined as (no_key = true)
.
If no argument is passed to the method, no_key
is set to true
. If an argument is provided, no_key
remains unset, and key
is assigned the passed value.
When no_key
is true
, the method invokes super()
to call the superclass implementation of default
without any arguments.
The parentheses in super()
are crucial here because they explicitly indicate that no arguments should be passed to the parent method.
This ensures that Hash#default
is called as a getter to retrieve the global default value of the hash, rather than attempting to process any arguments. Without the parentheses, Ruby might pass unintended arguments, leading to incorrect behavior.
While this syntax offers a lot of flexibility, I am not particularly a fan of it, as it clearly breaks the "principle of least surprise."
However, it is important to cover this topic so you can be equipped if you encounter it in the near future.
Subscribe to RubyCademy and get free access to all our courses, plus hundreds of fun Ruby cards, quizzes, guides, and tutorials!
RubyCademy ©