Ruby's defined? Keyword

How to Check if a Variable is Defined in Ruby with defined? Keyword

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

3 min read

Ruby provides a handy defined?(expression) keyword that tests if the expression refers to anything recognizable. The expression can be an object, a variable, a method name, etc. If Ruby can’t resolve the expression, it returns nil. Otherwise, it returns a string describing the expression.

Here are some examples of using defined? with different types of expressions.

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

defined? 'Ruby'                 # 'expression'
defined?(some_random_variable)  # nil

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

site = nil
defined? site                   # 'local-variable'

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

Lazy-Evaluation + Caching of 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

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

It works... most of the time. If you have an 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

In such cases, the defined? method comes in handy. 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.

Don’t use defined? to check hash keys

A common mistake is to use defined? to verify if a hash key is defined. For example,

hash = {}

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

# Output
# 
# 'random_key exists'

This is because it returns 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.

Use parenthesis when using defined?

Since Ruby is so lenient, you don’t always need to use them, 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 liked this article and you learned something new. If you're new to the blog, check out the start here page for a guided tour or browse the full archive to see all the posts I've written so far.

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 receive from developers, and look forward to hearing from you.

If you'd like to receive future articles directly in your email, please subscribe to my blog. If you're already a subscriber, thank you.