TL;DR
Local variables are defined when encountered by the parser, while constants are not. However, both must be defined when evaluated by the interpreter to avoid NameError.
Analysis
Local Variables are Auto-Vivified by the Parser
Your original code doesn't actually assign a value to either the local variable or the constant. In both cases, if false
is never truthy, so the assigment statements are never executed. Undefined variables and constants are handled differently by the parser, though.
Scoping issues aside, local variables are created when the parser encounters the assignment, not just when the assignment occurs. So, even though:
if false
var = "Hello"
end
never executes the assignment, it still initializes the local variable to nil
.
Constants, on the other hand, are treated differently. An unknown constant (really, anything that starts with an uppercase letter) that isn't available within the current namespace will raise a NameError.
In a fresh irb session, both of these will raise NameError, but with slightly different exception messages:
puts var
#=> NameError (undefined local variable or method `var' for main:Object)
puts MY_CONST
#=> NameError (uninitialized constant MY_CONST)
However, if you change your branch logic so that an expression with an undefined variable is evaluated by the interpreter, you'll also get NameError:
if baz
puts true
end
#=> NameError (undefined local variable or method `baz' for main:Object)
Another Way to Examine the Behavior
Fire up a fresh irb session. Then:
irb(main):001:0> defined? var
#=> nil
irb(main):002:0> if false then var = 1 end
#=> nil
irb(main):003:0> defined? var
#=> "local-variable"
You can see that var is both defined and set to nil
when encountered by the parser, even though the assignment expression is never evaluated. The constant is not auto-vivified, though:
irb(main):004:0> defined? MY_CONST
#=> nil
irb(main):005:0> if false then MY_CONST = 1 end
#=> nil
irb(main):006:0> defined? MY_CONST
#=> nil
irb(main):007:0> MY_CONST
#=> NameError (uninitialized constant MY_CONST)
Conclusions
While I would guess this behavior has to do with differences between the parser and the interpreter, and perhaps between the namespaces used for variable/method lookup vs. constant lookup, I can't really tell you why the difference is necessary (if it really is), or even if it's the same across all Ruby implementations. That's a question for the various Ruby engine developers, including the Ruby Core Team.
Pragmatically, though, you will always get a NameError exception when you attempt to use an undefined variable or constant. The real-world impact (if any) of this difference is therefore minimal. All languages have quirks; this may be one of them, but it's hard to see how this would cause practical problems outside of contrived examples. Your mileage may certainly vary.