Ruby warnings and scope

Today I wrote some Ruby code at work. We use Ruby 1.8 for reasons I won’t go into here. The code works fine, no issues. But we use Rubocop (under Ruby 1.9) to enforce consistent styles and also to look for any kind of oddity situations that might bite us.

Today Rubocop complained at me about my code. Likewise, many versions of Ruby complained in the same manner, excluding Ruby 1.8. Since the actual code I wrote wouldn’t make any sense to readers, I wrote a very easy-to-understand test case that reproduces the problem reliably, with lines 7 and 10 highlighted:

# Encoding: UTF-8

foo = true

if foo
  bar = 'something'
  puts bar
  [1, 2, 3].each { |bar| puts 'hello' }

The warning message, both from Rubocop under Ruby 1.9, and from multiple versions of Ruby via ruby -w natively:

jdc@ubuntu:~$ rvm list

rvm rubies

=* ruby-1.8.7-p374 [ x86_64 ]
   ruby-1.9.3-p547 [ x86_64 ]
   ruby-2.1.0      [ x86_64 ]
   ruby-2.1-head   [ x86_64 ]

# => - current
# =* - current && default
#  * - default

jdc@ubuntu:~$ rvm 1.8.7-p374 do ruby -w ./x

jdc@ubuntu:~$ rvm 1.9.3-p547 do ruby -w ./x
./x:10: warning: shadowing outer local variable - bar

jdc@ubuntu:~$ rvm 2.1.0 do ruby -w ./x
./x:10: warning: shadowing outer local variable - bar

jdc@ubuntu:~$ rvm 2.1-head do ruby -w ./x
./x:10: warning: shadowing outer local variable - bar

jdc@ubuntu:~$ rvm 1.9.3-p547 do rubocop ./x
Inspecting 1 file


x:10:21: W: Shadowing outer local variable - bar
[1, 2, 3].each { |bar| puts 'hello' }

1 file inspected, 1 offense detected

I’m a C programmer (and also do assembly and Perl), so am quite familiar with -Wshadow, so the message itself made sense (most of what I found on Google were Ruby coders not understanding the message). But what didn’t make sense is that there was no shadowing violation here — the code within the if has a different scope than within the else. There is no way possible for these two to “share” scope, unless the language was horribly brain-damaged.

Simply renaming the variable bar (in either scope, but not both) rectifies the “problem”.

So, Ruby… do you simply not understand scope, or does your warning mode — which it appears very few people use (otherwise I’d have expected this to come up by now, and that’s pretty disappointing in itself) — simply have bugs (someone’s bison/yacc code is broken somewhere)?

The answer, as I found out in mid-2017, is that Ruby’s definition/concept of variable scope is unlike any other programming language I’ve used — and, if you ask me, borders on dangerous (I personally would use much more colourful language). In Ruby, variable scope segregation is limited to functions, classes, modules, procs, and blocks. Thus, in the above example, the if/else “sections” technically all have the same scope (i.e. main).

Equally dangerous and stupid, I also learned that Ruby internally declares the existence of a variable even if that code never gets run (i.e. the internal creation of said variable happens at parse-time, not run-time). As such, variables which are declared (but not used or not ever assigned to a value during run-time) end up with a value of nil (rather than throwing a variable-is-unknown error). A variable existing and assigned to nil is substantially different than a non-existent variable. With this design choice in mind, how can one trust the defined? expression? (answer: one probably can’t).

It never ceases to amaze me how much advocacy there is for Ruby, that has either awful design decisions (here’s another one) or bugs; it doesn’t matter to me which. No PL is perfect, but I would expect a language that’s been around since 1995 (with Ruby 1.8 being out as of late 2003) to have their ducks in order by now.

It’s amazing how nearly every single time I have to deal with Ruby, I find or encounter something like this.