Skip to content

Instantly share code, notes, and snippets.

@sdball
Last active March 16, 2019 01:35
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sdball/5162135 to your computer and use it in GitHub Desktop.
Save sdball/5162135 to your computer and use it in GitHub Desktop.
Sometimes Ruby is Weird
class NilWhatAreYouDoingStahp
attr_accessor :not_nil
def initialize
@not_nil = "I'm not nil! I'm a value!"
end
def wat
if not_nil.nil?
not_nil = "Ok, now I'm not nil."
end
not_nil
end
def omglol
not_nil
end
end
stahp = NilWhatAreYouDoingStahp.new
stahp.wat # => nil
stahp.omglol # => "I'm not nil! I'm a value!"
@sdball
Copy link
Author

sdball commented Mar 14, 2013

The puzzle: Why does calling wat return nil?

It's actually a very straightforward answer, just a bit surprising.

@sdball
Copy link
Author

sdball commented Mar 14, 2013

This comes to us from @Sparkboxx btw!

@olery
Copy link

olery commented Mar 18, 2013

Thanks for turning it into a fun puzzle instead of a head-banging-against-keyboard piece of code :-)

from @olery instead of @Sparkboxx, since I somehow can't swap the Gist page back to @Sparkboxx instead of the organization account

@yorickpeterse
Copy link

Although confusing at first this makes sense once you become familiar with the MRI internals (which is a bit of a sad requirement). This is because the Ruby parser pre scans scopes for the assignments of variables and processes them up to a certain extend. It doesn't assign any values yet though, instead it sets them to their default values (NilClass in this case).

In this particular bit of code Ruby sees that there's a possible local variable assignment in Foo#wat and as such pre-creates it. You can test this by adding p local_variables between lines 8 and 9 and you'll see that it displays [:not_nil].

Normally this wouldn't be an issue but since your local variable and the getter method are named the same way you get a conflict. In Ruby local variables have precedence over methods in the same scope. As a result this method will return the value of the local variable and not the getter.

To work around this issue one would have to use the following code:

class NilWhatAreYouDoingStahp
  attr_accessor :not_nil

  def initialize
    @not_nil = "I'm not nil! I'm a value!"
  end

  def wat
    # note the use of "self.", it's important in this case.
    if self.not_nil.nil?
      self.not_nil = "Ok, now I'm not nil."
    end
    self.not_nil
  end

  def omglol
    not_nil
  end
end

stahp = NilWhatAreYouDoingStahp.new
p stahp.wat
stahp.omglol

An even better alternative would be to use local variables with a different name.

@sdball
Copy link
Author

sdball commented Mar 18, 2013

Spot on! Another fun way to present this puzzle is to say that adding five characters makes the code work as expected. That's almost a giveaway though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment