t3knoid
t3knoid

Reputation: 23

Odd behavior with Ruby defined?

I am a newbie at Ruby and have a question with the defined? keyword.

Here's a snippet of code that I've written to load a yaml file to initialize settings in my Ruby script:

# Read settings file
require 'YAML'
settingsFile = File.join(File.dirname(__FILE__), "settings.yml").tr('\\', '/')
Settings = YAML.load_file(settingsFile) unless defined? Settings
puts Settings

The yaml file looks like this:

Hello: World

This outputs correctly with:

{"Hello"=>"world"}

Now if I use a variable instead of a constant to store the settings, such as the following:

# Read settings file
require 'YAML'
settingsFile = File.join(File.dirname(__FILE__), "settings.yml").tr('\\', '/')
settings = YAML.load_file(settingsFile) unless defined? settings
puts settings

settings returns empty.

What gives? Why would using a constant make this work?

Upvotes: 2

Views: 46

Answers (1)

tadman
tadman

Reputation: 211580

This is a quirk in the way Ruby handles trailing if/unless conditions and how variables come into existence and get "defined".

In the first case the constant is not "defined" until it's assigned a value. The only way to create a constant is to say:

CONSTANT = :value

Variables behave differently and some would argue a lot more strangely. They come into existence if they're used anywhere in a scope, even in blocks of code that get skipped by logical conditions.

In the case of your line of the form:

variable = :value unless defined?(variable)

The variable gets "defined" since it exists on the very line that's being executed, it's going to be conditionally assigned to. For that to happen it must be a local variable.

If you rework it like this:

unless defined?(variable)
  variable = :value
end

Then the behaviour goes away, the assignment proceeds because the variable was not defined prior to that line.

What's strange is this:

if defined?(variable)
  variable = :value
end

Now obviously it's not defined, it doesn't get assigned, but then this happens:

defined?(variable)
# => "local-variable"

Now it's defined anyway because Ruby's certain it's a variable. It doesn't have a value yet, it's nil, but it's "defined" as far as Ruby's concerned.

It gets even stranger:

defined?(variable)
# => false
if (false)
  variable = :value
end
defined?(variable)
# => "local-variable"

Where that block of code didn't even run, it can't even run, and yet, behold, variable is now defined. From that assignment line on forward that variable exists as far as Ruby is concerned.

Since you're doing an attempted assignment and defined? on the same line the variable exists and your assignment won't happen. It's like a tiny paradox.

Upvotes: 7

Related Questions