Ruby Quiz

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

Unlock Your Ruby Potential

Subscribe to RubyCademy and get free access to all our courses, plus hundreds of fun Ruby cards, quizzes, guides, and tutorials!

Explanation

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.

How does Rails use this idiom?

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.

Conclusion

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.

Unlock Your Ruby Potential

Subscribe to RubyCademy and get free access to all our courses, plus hundreds of fun Ruby cards, quizzes, guides, and tutorials!

RubyCademy ©