Introduction
Class variables in Ruby are prefixed with @@ and are shared across the class that defines them and all its subclasses. They’re one of Ruby’s most misunderstood features โ powerful in specific use cases, but dangerous when misused. This guide covers how they work, their pitfalls, and when to use them versus class instance variables.
Basic Usage
class HelloCount
@@count = 0
def self.count
@@count
end
def initialize(myname = "Ruby")
@name = myname
end
def hello
@@count += 1
puts "Hello, world, I am #{@name}"
end
end
bob = HelloCount.new("Bob")
alice = HelloCount.new("Alice")
ruby = HelloCount.new
p HelloCount.count # => 0
bob.hello # => Hello, world, I am Bob
alice.hello # => Hello, world, I am Alice
ruby.hello # => Hello, world, I am Ruby
p HelloCount.count # => 3
@@count is shared across all instances of HelloCount. Every call to hello increments the same counter.
Accessing Class Variables
Class variables can be read and written from:
- Class methods (
def self.method) - Instance methods (
def method) - The class body itself
class Config
@@debug = false
@@version = "1.0.0"
# Read from class method
def self.debug?
@@debug
end
# Write from class method
def self.enable_debug!
@@debug = true
end
# Read from instance method
def version
@@version
end
end
Config.debug? # => false
Config.enable_debug!
Config.debug? # => true
Config.new.version # => "1.0.0"
The Inheritance Problem
The most important thing to understand about @@var: it is shared across the entire inheritance hierarchy. Any subclass can read and modify it, and changes are visible everywhere:
class Animal
@@count = 0
def initialize
@@count += 1
end
def self.count
@@count
end
end
class Dog < Animal; end
class Cat < Animal; end
Dog.new
Dog.new
Cat.new
puts Animal.count # => 3
puts Dog.count # => 3 (same variable!)
puts Cat.count # => 3 (same variable!)
This shared behavior is often surprising. If a subclass reassigns the variable, it affects the parent:
class Vehicle
@@wheels = 4
def self.wheels; @@wheels; end
end
class Motorcycle < Vehicle
@@wheels = 2 # modifies Vehicle's @@wheels too!
end
puts Vehicle.wheels # => 2 (changed by Motorcycle!)
puts Motorcycle.wheels # => 2
This is why class variables are sometimes called “global variables with a class-scoped name.”
Thread Safety
Class variables are not thread-safe. If multiple threads increment @@count simultaneously, you can get race conditions:
class Counter
@@count = 0
def self.increment
@@count += 1 # NOT atomic โ read-modify-write race condition
end
def self.count
@@count
end
end
# In a multi-threaded environment, this can produce wrong results
threads = 100.times.map { Thread.new { Counter.increment } }
threads.each(&:join)
puts Counter.count # might not be 100!
Fix with a Mutex:
class Counter
@@count = 0
@@mutex = Mutex.new
def self.increment
@@mutex.synchronize { @@count += 1 }
end
def self.count
@@count
end
end
Inspecting Class Variables
class MyClass
@@foo = 1
@@bar = 2
end
MyClass.class_variables # => [:@@foo, :@@bar]
MyClass.class_variable_get(:@@foo) # => 1
MyClass.class_variable_set(:@@foo, 99)
MyClass.class_variable_defined?(:@@foo) # => true
When Class Variables Make Sense
Despite their pitfalls, class variables are appropriate in a few cases:
1. Truly shared counters across a hierarchy
class ActiveRecord::Base
@@connection_pool = ConnectionPool.new
# All models share the same connection pool โ intentional
end
2. Configuration shared across all subclasses
class APIClient
@@base_url = "https://api.example.com"
@@timeout = 30
def self.base_url; @@base_url; end
def self.timeout; @@timeout; end
end
class UserClient < APIClient; end
class OrderClient < APIClient; end
# All clients share the same base URL and timeout
puts UserClient.base_url # => "https://api.example.com"
puts OrderClient.timeout # => 30
Class Variables vs Class Instance Variables
In most cases, class instance variables (@var) are the better choice:
# Class variable โ shared, dangerous
class BadCounter
@@count = 0
def self.count; @@count; end
def self.increment; @@count += 1; end
end
class BadSubCounter < BadCounter; end
BadCounter.increment
puts BadSubCounter.count # => 1 (shared โ probably not what you want)
# Class instance variable โ per-class, safe
class GoodCounter
@count = 0
def self.count; @count ||= 0; end
def self.increment; @count = count + 1; end
end
class GoodSubCounter < GoodCounter
@count = 0 # own counter
end
GoodCounter.increment
puts GoodSubCounter.count # => 0 (independent)
puts GoodCounter.count # => 1
Comparison Table
| Feature | @@class_variable |
@class_instance_variable |
|---|---|---|
| Shared with subclasses | Yes โ all share one variable | No โ each class has its own |
| Subclass can change parent | Yes | No |
| Accessible from instance methods | Yes (directly) | No (need self.class.@var) |
| Thread safety | No (needs Mutex) | No (needs Mutex) |
| Recommended | Rarely | Usually preferred |
The Recommendation
Prefer class instance variables (@var) over class variables (@@var) in almost all cases. The Ruby Style Guide explicitly recommends avoiding class variables due to their surprising inheritance behavior.
# Avoid this
class MyClass
@@config = {}
end
# Prefer this
class MyClass
@config = {}
def self.config; @config; end
end
Resources
- Ruby Docs: Class Variables
- Ruby Style Guide: No Class Variables
- Metaprogramming Ruby โ Paolo Perrotta
Comments