In Progress
Unit 1, Lesson 1
In Progress

Autovivification

Video transcript & code

Ruby goes to great lengths to be a low ceremony language. For instance, in Ruby there is no need to declare the types of variables or methods. There is no need to declare the instance variables in a class. And there is no need to declare local variables before they are assigned.

As an example, we can reference instance variables or global variables that don't exist.

@foo                            # => nil
$bar                            # => nil

Rather than raising an exception, Ruby treats these variables as if they have values of nil.

It might seem as if what is happening here is that Ruby is spontaneously causing these variables to exist the moment they are referenced. The technical term for this kind of spontaneous generation is autovivification. Meaning that a variable is "automatically brought to life".

But if we ask Ruby whether the instance variable we just referenced exists, it claims it does not.

And if we ask for a list of the current instance variables, Ruby says there are none.

@foo                            # => nil
instance_variable_defined?("@foo") # => false
instance_variables              # => []

So what is happening here is not really autovivification. Ruby is not causing these variables to be brought to life. Instead, it simply treats any missing instance variable as if it has a value of nil.

As you no doubt know, Ruby behaves a little differently when it comes to local variables. Trying to reference a nonexistent local variable results in a name error.

foo                             # =>

# ~> NameError
# ~> undefined local variable or method `foo' for main:Object
# ~>
# ~> xmptmp-in26383sFG.rb:1:in `<main>'

And this is probably for the best. If Ruby silently returned nil for every unrecognized local variable name, we'd have a terrible time hunting down typos.

However, there is a case where Ruby does, in fact, autovivify local variables. And if you don't know what's going on, you might be taken by surprise by Ruby's behavior.

Here's an example.

We assign a local variable x.

We write an if statement, saying that if x is even, a new variable result should be set to the the value of x divided by two.

Then we check the value of result.

Not surprisingly, the value is 1.

And if we ask Ruby for a list of local variables, we see exactly what we expect: x and result.

x = 2
if x.even?
  result = x / 2
end

result                          # => 1

local_variables                 # => [:x, :result]

Now let's set x to 3. Before we execute this, let's think about what we expect to happen.

Since x is now odd, we expect that the if body will not be executed. Since it won't be executed, we expect that the result variable will not be assigned. Since it is never assigned, we expect that this variable will not be defined, and accessing it will raise a NameError just like we saw before.

Let's check our assumptions.

x = 3
if x.even?
  result = "even"
end

result                          # => nil

This is not what we expected!

Instead of a NameError, we see that result has a value of nil.

Has Ruby suddenly started treating local variables the way it treats instance variables? Is it now acting as if any unset local variable has a value of nil?

Let's check. We'll ask Ruby what local variables are defined.

x = 3
if x.even?
  result = "even"
end

local_variables                 # => [:x, :result]

Ruby says that both x and result are defined, just like before.

But how can this be?! We know that no assignment to result ever took place.

What we've run into here is a very special rule in how Ruby evaluates code. In a nutshell, the rule says that at a given line in the code, if there is any possibility that a local variable will exist, then that variable is guaranteed to always exist. Even if Ruby has to quietly assign it a nil value to ensure its existence.

In effect when Ruby sees a case where a local variable might be created, Ruby rewrites our code to behave like this:

x = 3
result = nil
if x.even?
  result = "even"
end

Here, the result is always assigned a nil value.

Then the value is optionally overridden if x turns out to be an even number.

In effect, Ruby autovivifies the result variable.

This rule has some interesting implications. For instance, check out this code. We have a simple method which takes an object and just sends #to_s to it.

Then we write foo = stringify(foo).

Surely this can't be valid, right? We're assigning the result of the method to a new variable, but we're also using that variable as an input to the method!

And yet, when we execute this, it works just fine, and returns the string equivalent of nil.

def stringify(o)
  o.to_s
end

foo = stringify(foo)
# => ""

This is another example of Ruby's implicit rewriting rules in action. Ruby ensures that it's not possible for a variable to be both existent and nonexistent on the same line of code.

So what do these strange rules about assignment mean for our code? Well, not a lot really.

It does mean that there's no need to write code like this, where we provide an else clause to assign a variable the value nil in case an if evaluates to false.

Ruby will take care of this for us, so we can omit the else.

if x.even?
  result = "even"
else
  result = nil
end

Other than that, the main value of knowing about this autovivification rule is that it can save us from confusion. If you've ever run into case where a variable was set to nil even though no code had run which should have set it, now you know how it happened.

Before I go, I'd like to thank Todd A. Jacobs for suggesting today's topic. Thanks Todd, and happy hacking!

Responses