In Progress
Unit 1, Lesson 1
In Progress

Class Variable

Have you ever been baffled by the way Ruby ‘@@‘ class variables behave? Have your been told by a senior developer or by a team coding standard that you shouldn’t use class variables, and wondered why?

In this episode we’ll clear up the mysteries around Ruby class variables once and for all. You’ll learn why many Ruby programmers avoid them. And you’ll learn a safer and less surprising alternative.

Video transcript & code

If you came to Ruby from any other common object-oriented programming language, there are certain features you probably expected to find. And as you got up to speed on the language, you quickly found them.

You expected to find classes, and on those classes, instance methods.

class Greeter
  def greet
    puts "hello, world"
  end
end

Greeter.new.greet
# >> hello, world

Along with the instance methods, there are also class methods.

class Greeter
  def greet
    puts "hello, world"
  end

  def self.perform
    new.greet
  end
end

Greeter.perform

# >> hello, world

(Of course, if you remember episode #454, you know that these aren't really class methods in the classical sense. But we'll let that slide for now.)

Objects also have instance variables, also called "ivars".

class Greeter
  def initialize(name)
    @name = name
  end

  def greet
    puts "hello, #{@name}"
  end

  def self.perform
    new("world").greet
  end
end

Greeter.perform

# >> hello, world

And to go with instance variables, there are also class variables.

class Greeter
  @@salutation = "hello"

  def initialize(name)
    @name = name
  end

  def greet
    puts "#{@@salutation}, #{@name}"
  end

  def self.perform
    new("world").greet
  end
end

Greeter.perform

# >> hello, world

Now, if you do remember episode #454, you might think I'm going to tell you that these double-@ variables somehow aren't real class variables. But unlike Ruby "class methods", these are the real deal.

Unfortunately, that fact can lead to some surprising behavior.

Let's say we have two classes, Tweedledee and Tweedledum.

These two classes might each have some unique behavior of their own, but they also share some identical code in common.

Specifically, each one owns a @@count class variable, and has methods to increment and access the value of that variable.

Each of these classes should maintain its own distinct count. Let's make sure they do.

We'll check the starting value of Tweedledee, and then increment it by one.

Then we'll do the same for Tweedledum, except we'll increment it twice.

Now let's check the final counts.

Tweedledee is at… 1.

And Tweedledum is at… 2.

class Tweedledee
  @@count = 0

  def self.increment
    @@count += 1
  end

  def self.count
    @@count
  end

  # more code...
end

class Tweedledum
  @@count = 0

  def self.increment
    @@count += 1
  end

  def self.count
    @@count
  end

  # more code...
end

Tweedledee.count                # => 0
Tweedledee.increment            # => 1

Tweedledum.count                # => 0
Tweedledum.increment            # => 1
Tweedledum.increment            # => 2

Tweedledee.count                # => 1
Tweedledum.count                # => 2

Everything is looking good here.

The only problem is, these two classes share an awful lot of identical code.

Let's move that code out into a module.

We'll move the counter variable exactly as-is.

We'll move the class methods, and change them into instance methods.

We can't keep them as class methods, because module inheritance only adds instance methods, not class methods.

Then, since we want to add class-level methods to our target classes, we use extend instead of include to enhance Tweedledee and Tweedledum with the newly extracted module.

Let's run our test code again.

Uh-oh.

Instead of Tweedledee having a count of 1 and Tweedledum having a count of 2, now both classes share a count of 3.

module Counter
  @@count = 0

  def increment
    @@count += 1
  end

  def count
    @@count
  end
end

class Tweedledee
  extend Counter
end

class Tweedledum
  extend Counter
end

Tweedledee.count                # => 0
Tweedledee.increment            # => 1

Tweedledum.count                # => 1
Tweedledum.increment            # => 2
Tweedledum.increment            # => 3

Tweedledee.count                # => 3
Tweedledum.count                # => 3

Something has gone wrong here. It turns out, Ruby class variables aren't just class variables; they can be set in modules as well. And when two classes inherit code that accesses a class or module variable, they don't get their own copy! They all reference the exact same original variable.

So how do we get the behavior we want?

Well, let's start with some investigation.

We can set a class variable inside a class…

…and then read that value later.

class Tweedledee
  @@class_var = 123
end

class Tweedledee
  @@class_var                  # => 123
end

Now let's recall the context we're in when we write code inside a class block.

The value of self inside a class is: the class.

And what is a class? Well, it's an object. Specifically, it's an instance of the class named Class.

class Tweedledee
  @@class_var = 123
  self                          # => Tweedledee
  self.class                    # => Class
end

And what can we do with instances?

Well, for one thing, we can set instance variables on them.

And we can retrieve the value of those variables later.

class Tweedledee
  @class_ivar = 456
end

class Tweedledee
  @class_ivar                  # => 456
end

Pay careful attention here, because there's something that's easy to miss: when Ruby sees a variable with a single @-sign on it, it always looks up that variable in the current self object. Always.

And we are not inside an instance method definition right now. We're in the class definition block, outside any methods. Which means when we reference an instance variable, we're referencing instance variables attached directly to those classes. Not of instances of those classes!

Now let's run a little experiment. We'll set up a Base class.

Inside it, we'll set both a class variable and an instance variable.

Then we'll inherit a class from the base, called A.

Inside it, We'll update both the class variable and the instance variable.

Next we'll inherit another class, called B.

Again, we'll set both the class variable and the class instance variable.

Now let's examine the outcome of this code.

Inside the Base class, the class variable now says that it was set in class B. The instance variable, on the other hand, still has the value set in the Base.

Inside class A, the class variable also has the value set in class B. But the instance variable shows the value that was set in A.

And in class B, we see that both variables show the value initially set in class B.

class Base
  @@class_var    = "set in Base"
  @class_ivar    = "set in Base"
end

class A < Base
  @@class_var = "set in A"
  @class_ivar = "set in A"
end


class B < Base
  @@class_var = "set in B"
  @class_ivar = "set in B"
end

class Base
  @@class_var                   # => "set in B"
  @class_ivar                   # => "set in Base"
end

class A
  @@class_var                   # => "set in B"
  @class_ivar                   # => "set in A"
end

class B
  @@class_var                   # => "set in B"
  @class_ivar                   # => "set in B"
end

What we can see here is that all of these classes share the same class variable through inheritance, and the last one to set it "wins". But when it comes to class instance variables, each class object gets its own distinct value.

Before we move on, let's do one more experiment.

We'll strip out all the instance variables.

Then we'll remove the class variable initialization from the base class.

Now, when we take a look at the class variable values for classes A and B, we can see that they both have their own distinct values.

But now, after those values have been set, let's now belatedly set the Base value of the class variable.

And let's once again check the values of the variable in A and B.

class Base
end

class A < Base
  @@class_var = "set in A"
end


class B < Base
  @@class_var = "set in B"
end

class A
  @@class_var                   # => "set in A"
end

class B
  @@class_var                   # => "set in B"
end

class Base
  @@class_var = "set later in Base"
end

class A
  @@class_var                   # => "set later in Base"
end

class B
  @@class_var                   # => "set later in Base"
end

What we can see is that after we set the class variable higher up in the inheritance tree, that value wiped out the former individual values of the classes below it.

If you are thinking that this is a recipe for confusing behavior, especially if source files are loaded in variable orders… you are not wrong!

OK, so knowing what we do now, let's see if we can extract our original class counter functionality into a module that uses class instance variables, instead of class variables.

We'll once again define a Counter module.

We'll initialize an instance variable to 0.

We'll provide increment and a reader method.

Then, as before, we'll extend the Tweedledee and Tweedledum classes with our module. By using extend, we add the module functionality to the class level rather than the instance level.

Let's take a look at the initial count value on one of our classes, and then immediately increment it.

module Counter
  def initialize
    @count = 0
  end

  def increment
    @count += 1 # ~> NoMethodError: undefined method `+' for nil:NilClass
  end

  def count
    @count
  end
end

class Tweedledee
  extend Counter
end

class Tweedledum
  extend Counter
end

Tweedledee.count                # => nil
Tweedledee.increment            # =>
# ~> NoMethodError
# ~> undefined method `+' for nil:NilClass
# ~>
# ~> xmptmp-in14751QvN.rb:7:in `increment'
# ~> xmptmp-in14751QvN.rb:24:in `<main>'

Uh-oh. The count is nil. It was supposed to be 0. What happened?

We've run into one of the classic gotchas of working with Ruby modules: there is no guarantee that an initialize method defined in a module will ever be called by the classes that include or extend the module. Our initializer method was never executed, and so our counter was nil.

Fortunately, this is an easy fix. We redefine the count method to default the count to 0 if it isn't already set.

And we rewrite the increment method to make sure that it references the count method. That way, if the count wasn't initialized already, it will be.

Now let's put these classes through their paces.

The starting count for Tweedledee is 0.

We'll increment it by 1.

The starting count for Tweedledum is also 0.

We'll increment it twice.

Now, the moment of truth. We check the values of both counters, and…

…they both have their expected, individual values.

module Counter
  def increment
    @count = count + 1
  end

  def count
    (@count ||= 0)
  end
end

class Tweedledee
  extend Counter
end

class Tweedledum
  extend Counter
end

Tweedledee.count                # => 0
Tweedledee.increment            # => 1

Tweedledum.count                # => 0
Tweedledum.increment            # => 1
Tweedledum.increment            # => 2

Tweedledee.count                # => 1
Tweedledum.count                # => 2

Victory at last.

So now we've learned that Ruby has both class variables and class instance variables. We've seen that class variable values are inherited, leading to sometimes surprising behavior. But each class gets its own private copy of its instance variables.

This is the part where I lay out a thoughtful set of guidelines for when to use a class variable, versus when to use class instance variables. And it turns out that the rule is really simple. It's this:

Don't use Ruby class variables.

Just don't.

Ruby's class variables are one of those language features that seemed important and even necessary when the language was created. But long experience has shown them to be more surprising than useful. I can't remember the last time I used a class variable to solve a programming problem. And many Ruby programmers consider it a code smell when they find class variables used in a codebase.

Anything you might want to use a class variable for, you can also do with class instance variables, and the result is likely to be less error-prone. It might take a little bit of extra code, but the trade-off is probably worth it.

I feel a little bad that I took all this time to explain a feature to you, only to tell you not to use it. But class variables are one of the more common causes of confusion to novice and intermediate Ruby programmers, and it's hard to understand why you should avoid them unless you have a solid grasp of exactly how they work. Happy hacking!

Responses