Global Functions in Ruby

Update: I've discovered a flaw in my understanding of Ruby's handling of global functions. View the updated entry for a less incorrect explanation.

I'm a little late for my self-imposed weekly blog posting, but I've just discovered something interesting about Ruby's resolution of instance variables.

Here are three Ruby scripts:

Script 1Script 2Script 3

def foo
  @foo
end


class Bar
  def initialize
    @foo = 42
  end
  def bar
    foo
  end
end

p foo
p Bar.new.bar
$main = self
def $main.foo
  @foo
end


class Bar
  def initialize
    @foo = 42
  end
  def bar
    $main.foo
  end
end

p $main.foo
p Bar.new.bar
$main = class Foo
  def foo
    @foo
  end
end.new

class Bar
  def initialize
    @foo = 42
  end
  def bar
    $main.foo
  end
end

p $main.foo
p Bar.new.bar

What do you suppose they output?

An experience rubyist would recognise that the first call to #foo will return nil in all three. The magic of Ruby ensures a few things:

  1. syntactically @foo can always only be an instance variable, so there's none of that foo if (foo=1) nonsense about resolving methods vs. variables
  2. there's always an object (anything defined in the global scope is actually defined on the object called "main," which is an instance of Object) so there's no problem resolving the instance variable @foo in script 1
  3. any variable which hasn't had a value assigned to it results in nil. Practically, this can only apply to instance, class and global variables, because any "sigil-free" variables have to have been assigned for the parser to recognise them as variables.

The problem interestingness is in the second call. Bar#bar calls #foo in exactly the same way we just did, so one would expect the @foo variable to be bound to the main object in the same way. In scripts 2 and 3 this is in fact the case; the explicit receiver (i.e. the $main.) ensures it. However script 1 is weird.

Because #foo in script 1 is declared as a global method, it is only tenuously bound to the main object. When it is called from inside the scope of a Bar method, Ruby seems to search that Bar object's instance variables before stepping out to main's. So the output of the three scripts is:

Script 1Script 2Script 3
$ ruby script1.rb
nil
42
$ ruby script2.rb
nil
nil
$ ruby script3.rb
nil
nil

And now you know.


Matthew Kerwin

Published
Modified
License
CC BY-SA 4.0
Tags
development, gotcha, ruby, software

Comments powered by Disqus