Gus
Gus

Reputation: 954

Ruby conditions and defined? operator weird behavior

I want to set a variable if it's not already defined, so I write

if defined?(var).nil?
  var = true
end
puts "[#{var}]"

This behaves as expected, it will print [true]. But if I want to simplify the snippet and write:

var = true if defined?(var).nil? 
puts "[#{var}]"

It will print [].

What is the difference between these two snippets ?

Upvotes: 0

Views: 43

Answers (2)

Cary Swoveland
Cary Swoveland

Reputation: 110685

Consider the following.

a #=> NameError (undefined local variable or method `a' for main:Object)
defined?(a)
  #=> nil
a #=> NameError (undefined local variable or method `a' for main:Object)

As no local variable a exists defined? returns nil but does not create the variable.

b = 1
c = defined?(b)
  #=> "local-variable"
puts "cat" if c
cat

d = nil
e = defined?(nil)
  #=> "nil"
e.nil?
  #=> false

c ("local-variable") is truthy (logically true) because it is neither nil or false (the latter being falsy).

a #=> NameError (undefined local variable or method `a' for main:Object)
a = true if false
  #=> nil
a #=> nil

This last result is an odd characteristic of Ruby. As soon as the parser sees the beginning of the assignment (a =) it sets a equal to nil. I understand this is done for efficiency reasons.

Case 1

d = defined?(var1)
  #=> nil 
e = d.nil?
  #=> true 
var1 = true (since e #=> true)
var1
  #=> true

Case 2

var2 =
  # var2 is set equal to nil
d = defined?(var2)
  #=> "nil"
e = d.nil?
  #=> false 
var2 = true if false
var2
  #=> nil
puts "[#{nil}]"
[]

Upvotes: 1

Jörg W Mittag
Jörg W Mittag

Reputation: 369468

A local variable is defined from the point that the first assignment to it is parsed. Therefore, in your second snippet, the variable is defined at the point where you are calling defined? (since Ruby is parsed like English, i.e. left-to-right, top-to-bottom), therefore the condition is always false, therefore the assignment never gets executed, therefore, the variable never gets initialized. Un-initialized local variables evaluate to nil and nil.to_s is the empty string.

Upvotes: 3

Related Questions