Jacob Swanner Development Blog

Optional Parameter Gotchas

Assuming we have a class that looks like this:

class Foo
  attr_reader :options

  def initialize options = {}
    @options = options
  end
end

What does the following code output?

foo = Foo.new nil
puts foo.options.inspect

The output will be {}, right? Well, let’s check it out:

puts foo.options.inspect
# => nil

What the…? options was an optional parameter, and we set the default value to be an empty hash, what gives? What went wrong? Well, let’s try something a little different:

foo = Foo.new
puts foo.options.inspect
# => {}

This time we get the default value we were expecting, but why didn’t it work before? Well, with optional parameters, the default value is used if the argument is omitted; but, if an explicit nil is passed, that will be the parameter’s value. This behavior is not just for Hash default values, it applies to any default value.

Okay, so knowing that, how do we get around this issue? Well, the answer’s not too bad:

class Foo
  attr_reader :options

  def initialize options = nil
    @options = options || {}
  end
end

foo = Foo.new nil
puts foo.options.inspect
# => {}

So, this time we still have an optional parameter; but, we’re declaring the default value as nil, and when we set the @options instance variable we use || to make the value {}, if options is nil. This may seem like a small change, but can mean a big different in code correctness. If in some other part of our class we call methods on options thinking we’re guaranteed to have a Hash – or Hash-like object – but we in fact have nil, that could lead to some confusing errors.

Okay, so maybe now you are thinking: ‘but, I would never explicitly pass a nil like that.’ Well, you may not knowingly do so, but take a look at the following:

# somewhere else params is defined as: params = {}
Foo.new params[:foo]

In this scenario, maybe you thought you were passing a nested Hash into Foo.new, but if params doesn’t have a value for the key :foo, you’ve just passed a nil into Foo.new. This is just one example of how a nil could end up being explicitly passed as an argument, but it’s far from the only one.

I think the rule of thumb for default parameters should be:

  • Always declare the default value of an optional parameter as nil
  • Inside the body of the method, set the default value you actually want

And, that’s it really. I think that’s a pretty simple rule.