I was trying to read some Rails code today and came across the definition of the link_to view helper, which looks like this:
def link_to(*args, &block) if block_given? options = args.first || {} html_options = args.second concat(link_to(capture(&block), options, html_options)) else name = args.first options = args.second || {} html_options = args.third url = url_for(options) if html_options html_options = html_options.stringify_keys href = html_options['href'] convert_options_to_javascript!(html_options, url) tag_options = tag_options(html_options) else tag_options = nil end href_attr = "href=\"#{url}\"" unless href "<a #{href_attr}#{tag_options}>#{name || url}</a>" end end
At first glance, I thought for sure I had found a bug! The variable, href, is only initialized if html_options is specified. It seems like the “href_attr = … unless href” line would blow up otherwise, since it’s testing a variable that may not have been set. Or, so I thought. It turns out that my understanding of Ruby’s local variable semantics was wrong, as demonstrated by this simple test:
irb(main):001:0> x NameError: undefined local variable or method 'x' for main:Object from (irb):1 from :0 irb(main):002:0> if false irb(main):003:1> x = 0 irb(main):004:1> end => nil irb(main):005:0> x => nil
It seems that assigning to an uninitialized variable in code that does not execute is sufficient to create that variable and assign it a default value of nil. This is in contrast to Python, which doesn’t define new variables unless the code that sets them actually executes:
>>> x Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not defined >>> if False: ... x = 0 ... >>> x Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'x' is not defined
Neither does Javascript:
js> x typein:1: ReferenceError: x is not defined js> if (false) x = 0; js> x typein:3: ReferenceError: x is not defined
Is it just me, or is Ruby’s behavior completely bizarre here?