Ruby's defined? Keyword

Understanding Ruby's defined? Keyword

Ruby's defined? keyword is an elegant way to check if a variable is defined or not and also to cache expensive operations. However, it's confusing, and a few gotchas await the new Rubyist. Now that I've had a few opportunities to play with it, here’s my shot at sparing you some of the confusion.

4 min read

Ruby provides a handy defined?(arg) keyword that returns a string describing its argument.

language = 'Ruby'

defined? language               # 'local-variable'
defined? @instance_var          # 'instance-variable'
defined? @@class_var            # 'class variable'

defined? nil                    # 'nil'

defined? 1                      # 'expression'
defined? 'Ruby'                 # 'expression'
defined? String                 # 'constant'

You can use defined? to test if the expression refers to anything recognizable. The expression can be an object, a variable, a method name, etc.

Note that a variable set to nil is still initialized and recognized by ruby as a local-variable.

framework = nil

defined?(framework)  # 'local-variable'

However, if you pass an undefined variable, defined? returns nil. Similarly, if Ruby can’t resolve the expression, it returns nil.

defined?(some_random_variable)  # nil

With the basics out of the way, let's look at a common usage pattern of this keyword.

Usage: Lazy-Evaluation + Caching Expensive Operations

Sometimes, you want to lazily evaluate some code, only once. That is, do nothing if a variable exists but initialize it if it doesn’t.

The idiomatic ruby way to accomplish this is to use the ||= operator.

class Task
  def result
    @result ||= calculate_result
  end
  
  def calculate_result
    puts "heavy calculation here... should happen only once"
    
    100
  end
end

t = Task.new
puts t.result
puts t.result
puts t.result

# Output
#
# heavy calculation here... should happen only once
# 100
# 100
# 100

As you can see, calling the result method multiple times didn't call the expensive calculate_result method, since we had cached the result after the first call.

It works... most of the time.

If you have an expensive operation that can return nil or boolean value false as result, the cached method will be called each time you call result, eliminating the benefit of the ||= operator.

For example, let's change the calculate_result method above to return false (or nil) instead of 100 and see what happens.

class Task
  def result
    @result ||= calculate_result
  end
  
  def calculate_result
    puts "heavy calculation here... should happen only once"
    false  # or nil
  end
end

t = Task.new

puts t.result
puts t.result

# Output
#
# heavy calculation here... should happen only once
# false
# heavy calculation here... should happen only once
# false

After the first call, @result was false, a valid return value. Still, in later executions, because it evaluated to false, the Ruby interpreter called the expensive calculate_result method, which was unnecessary.

In such cases, the defined? method comes in handy.

Let's change the result method so it first checks if the @result variable is defined.

def result
  return @result if defined?(@result)
  @result = calculate_result
end

Now, the calculate_result will be executed only once. For the subsequent calls, the instance variable @result is defined, so Ruby won't execute the second line of code above, skipping the expensive operation.

Don’t use defined? to check hash keys

A common mistake is to use defined? to verify if a hash key is defined. Although Ruby devs might think it strange, this is especially common if you also write PHP on the side (like me - a big fan of Laravel) and are used to the isset() method.

Consider the following example.

hash = {}

if defined?(hash['random_key'])
  puts 'random_key exists'
else
  puts 'random_key missing'
end

# Output
# 
# 'random_key exists'

This is because the condition returned the string 'method', which ruby evaluates to true in a boolean expression.

hash = {}

defined?(hash['random_key'])  # 'method'

The idiomatic Ruby way to check if a key exists in a hash is to use any of the following methods: has_key?, key?, include?, or member?

hash = {}

if hash.include?('random_key')
  puts 'random_key exists'
else
  puts 'random_key missing'
end

# Output
# 
# 'random_key missing'

To learn more about the features and capabilities of a Ruby Hash, check out this article (discussion on Hacker News):

Ruby’s Hash is a Swiss-Army Knife
A Hash is a built-in data structure in Ruby that maps values to keys and has a constant-time O(1) lookup. This article shows the capabilities of this simple, but equally powerful tool. We’ll start with the basics but also cover some obscure but equally useful features of hash.

Should You use parenthesis with defined?

Since Ruby is so forgiving, you don’t always need to use parenthesis with defined?, but it’s highly recommended due to the low precedence of defined? keyword.

For example, the following test fails:

class TestDefined < Minitest::Test
  def test_precedence
    result = 10
    assert_equal true, defined? result && result > 0
  end
end

# Fails!
# expected: true
# got: "expression"

Adding parentheses results in a better check, and it also results in a clear and readable code. The following test passes with flying colors.

class TestDefined < Minitest::Test
  def test_precedence
    result = 10
    assert_equal true, defined?(result) && result > 0
  end
end

That's a wrap. I hope you found this article helpful and you learned something new.

As always, if you have any questions or feedback, didn't understand something, or found a mistake, please leave a comment below or send me an email. I reply to all emails I get from developers, and I look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. Your email is respected, never shared, rented, sold or spammed. If you're already a subscriber, thank you.