Overview
Class variables store data that belongs to a class rather than individual instances. Ruby creates a single copy of each class variable that all instances of the class share, along with instances of any subclasses. The @@
prefix identifies class variables, distinguishing them from instance variables (@
) and local variables.
Ruby initializes class variables when the class definition first executes. The variable exists throughout the program's lifetime once created, maintaining its value across all instances and method calls. Class variables differ from class instance variables by sharing state with subclasses rather than maintaining separate copies.
class Counter
@@count = 0
def initialize
@@count += 1
end
def total
@@count
end
end
first = Counter.new
# => @@count is now 1
second = Counter.new
# => @@count is now 2
first.total # => 2
second.total # => 2
Ruby's class variable implementation creates a single storage location shared across the entire class hierarchy. This sharing behavior extends to subclasses, which inherit access to parent class variables. The visibility rules allow access from class methods, instance methods, and class bodies.
class Vehicle
@@wheels = 4
def self.wheels
@@wheels
end
def wheels
@@wheels
end
end
class Motorcycle < Vehicle
@@wheels = 2 # Modifies the shared @@wheels variable
end
Vehicle.wheels # => 2 (changed by subclass)
Motorcycle.wheels # => 2
Class variables provide shared counters, configuration settings, and cached data across all instances. They serve as a mechanism for maintaining state that transcends individual object lifetimes while remaining encapsulated within the class hierarchy.
Basic Usage
Class variable declaration requires initialization within the class body before first use. Ruby raises a NameError
when accessing uninitialized class variables, unlike instance variables which return nil
. The initialization typically occurs at the top level of the class definition.
class DatabaseConnection
@@max_connections = 100
@@active_connections = 0
def initialize
if @@active_connections >= @@max_connections
raise "Connection limit exceeded"
end
@@active_connections += 1
end
def close
@@active_connections -= 1
end
def self.active_count
@@active_connections
end
def self.max_count
@@max_connections
end
end
conn1 = DatabaseConnection.new
conn2 = DatabaseConnection.new
DatabaseConnection.active_count # => 2
conn1.close
DatabaseConnection.active_count # => 1
Reading and writing class variables follows standard assignment syntax within any method scope. Class methods access class variables directly, while instance methods can read and modify the shared values. The defined?
operator checks for class variable existence.
class Settings
@@debug_mode = false
def self.enable_debug
@@debug_mode = true
end
def self.debug_enabled?
@@debug_mode
end
def log(message)
if @@debug_mode
puts "[DEBUG] #{message}"
end
end
def self.has_setting?(name)
defined?(@@debug_mode) ? true : false
end
end
Settings.debug_enabled? # => false
settings = Settings.new
settings.log("Test") # => (no output)
Settings.enable_debug
settings.log("Test") # => [DEBUG] Test
Class variables maintain their values between method calls and across different instances. Ruby preserves the state until the program terminates or the class gets redefined. This persistence makes class variables suitable for counters, flags, and shared configuration data.
class RequestTracker
@@request_count = 0
@@error_count = 0
def process_request
@@request_count += 1
if rand < 0.1 # 10% error rate
@@error_count += 1
raise "Request failed"
end
"Success"
rescue
"Error"
end
def self.stats
{
total: @@request_count,
errors: @@error_count,
success_rate: (@@request_count - @@error_count).to_f / @@request_count
}
end
end
tracker = RequestTracker.new
100.times { tracker.process_request }
RequestTracker.stats
# => {:total=>100, :errors=>8, :success_rate=>0.92}
Inheritance creates sharing relationships where subclasses access the same class variable storage as their parent classes. Modifications in any class within the hierarchy affect all related classes. This behavior differs from other object-oriented languages where subclasses typically get independent copies.
class Animal
@@population = 0
def initialize
@@population += 1
end
def self.population
@@population
end
end
class Dog < Animal
end
class Cat < Animal
end
Animal.population # => 0
Dog.new
Animal.population # => 1
Cat.new
Cat.new
Animal.population # => 3
Dog.population # => 3 (shares same variable)
Advanced Usage
Class variable inheritance creates complex sharing patterns when subclasses redefine variables with the same name. Ruby maintains a single variable per name within the class hierarchy, meaning subclass assignments modify the parent's variable rather than creating independent storage.
class ConfigManager
@@environment = "development"
@@features = {}
def self.set_feature(name, enabled)
@@features[name] = enabled
end
def self.feature_enabled?(name)
@@features[name] || false
end
def self.environment
@@environment
end
end
class ProductionConfig < ConfigManager
@@environment = "production" # Modifies parent's @@environment
def self.setup
set_feature(:logging, true)
set_feature(:caching, true)
set_feature(:debug, false)
end
end
class TestConfig < ConfigManager
def self.setup
set_feature(:logging, false)
set_feature(:debug, true)
end
end
ProductionConfig.setup
ConfigManager.environment # => "production" (changed by subclass)
ConfigManager.feature_enabled?(:logging) # => true
TestConfig.setup
ProductionConfig.feature_enabled?(:debug) # => true (shared state)
Metaprogramming with class variables enables dynamic variable creation and access. The class_variable_defined?
, class_variable_get
, and class_variable_set
methods provide programmatic control over class variables. These methods accept symbols or strings as variable names.
class DynamicRegistry
def self.register(type, implementation)
var_name = "@@#{type}_registry"
unless class_variable_defined?(var_name)
class_variable_set(var_name, {})
end
registry = class_variable_get(var_name)
registry[implementation.name] = implementation
end
def self.get_implementations(type)
var_name = "@@#{type}_registry"
if class_variable_defined?(var_name)
class_variable_get(var_name)
else
{}
end
end
def self.list_types
class_variables.map { |var| var.to_s.gsub(/@@|_registry/, '') }
end
end
class FileProcessor; end
class ImageProcessor; end
class VideoProcessor; end
DynamicRegistry.register(:processor, FileProcessor)
DynamicRegistry.register(:processor, ImageProcessor)
DynamicRegistry.register(:handler, VideoProcessor)
DynamicRegistry.get_implementations(:processor)
# => {"FileProcessor"=>FileProcessor, "ImageProcessor"=>ImageProcessor}
DynamicRegistry.list_types # => ["processor", "handler"]
Class variable scope resolution follows Ruby's method lookup chain, but with sharing semantics that create unexpected behaviors. When multiple classes in a hierarchy define variables with identical names, they reference the same storage location regardless of where the access occurs.
module Trackable
def self.included(base)
base.class_eval do
@@tracked_methods = []
end
end
def track_method(method_name)
@@tracked_methods << method_name unless @@tracked_methods.include?(method_name)
end
def tracked_methods
@@tracked_methods
end
end
class UserModel
include Trackable
def save
track_method(:save)
# save logic
end
def update
track_method(:update)
# update logic
end
end
class ProductModel
include Trackable
def save
track_method(:save)
# save logic
end
def delete
track_method(:delete)
# delete logic
end
end
user = UserModel.new
user.save
user.update
user.tracked_methods # => [:save, :update]
product = ProductModel.new
product.save
product.delete
product.tracked_methods # => [:save, :update, :delete] (shared with UserModel)
Class reopening and monkey patching affect existing class variables. When reopening a class that already contains class variables, the existing variables remain accessible and modifiable. New class variables can be added to existing classes through reopening.
class Logger
@@log_level = :info
def self.log_level
@@log_level
end
def self.log_level=(level)
@@log_level = level
end
def log(message)
puts "[#{@@log_level.upcase}] #{message}"
end
end
# Later in the application
class Logger
@@max_file_size = 1024 * 1024 # Add new class variable
@@current_file_size = 0
def self.rotate_if_needed
if @@current_file_size > @@max_file_size
# rotation logic
@@current_file_size = 0
end
end
def log(message)
super(message)
@@current_file_size += message.length
self.class.rotate_if_needed
end
end
logger = Logger.new
Logger.log_level = :debug
logger.log("Test message") # Uses modified behavior
Thread Safety & Concurrency
Class variables create shared mutable state across all threads, making them inherently unsafe for concurrent access without proper synchronization. Multiple threads can read and modify class variables simultaneously, leading to race conditions and data corruption.
require 'thread'
class Counter
@@count = 0
@@mutex = Mutex.new
def self.unsafe_increment
current = @@count
sleep(0.001) # Simulate processing time
@@count = current + 1
end
def self.safe_increment
@@mutex.synchronize do
current = @@count
sleep(0.001)
@@count = current + 1
end
end
def self.count
@@count
end
def self.reset
@@mutex.synchronize { @@count = 0 }
end
end
# Demonstrate race condition
Counter.reset
threads = 10.times.map do
Thread.new { 100.times { Counter.unsafe_increment } }
end
threads.each(&:join)
puts "Unsafe result: #{Counter.count}" # Usually less than 1000
# Demonstrate thread safety
Counter.reset
threads = 10.times.map do
Thread.new { 100.times { Counter.safe_increment } }
end
threads.each(&:join)
puts "Safe result: #{Counter.count}" # Always 1000
Read-modify-write operations on class variables require atomic protection to prevent lost updates. Simple assignment operations are atomic, but complex modifications involving multiple steps need synchronization mechanisms like mutexes or atomic operations.
class StatisticsTracker
@@stats = { requests: 0, errors: 0, total_time: 0.0 }
@@mutex = Mutex.new
def self.record_request(duration, error = false)
@@mutex.synchronize do
@@stats[:requests] += 1
@@stats[:errors] += 1 if error
@@stats[:total_time] += duration
end
end
def self.get_stats
@@mutex.synchronize do
{
requests: @@stats[:requests],
errors: @@stats[:errors],
error_rate: @@stats[:errors].to_f / @@stats[:requests],
average_time: @@stats[:total_time] / @@stats[:requests]
}
end
end
def self.reset_stats
@@mutex.synchronize do
@@stats = { requests: 0, errors: 0, total_time: 0.0 }
end
end
end
# Thread-safe usage
threads = 50.times.map do
Thread.new do
100.times do
duration = rand * 0.1
error = rand < 0.05
StatisticsTracker.record_request(duration, error)
end
end
end
threads.each(&:join)
puts StatisticsTracker.get_stats
Class hierarchies with shared class variables require careful consideration of synchronization scope. Subclasses sharing parent class variables must use the same synchronization mechanisms to maintain consistency across the inheritance chain.
class BaseService
@@active_requests = 0
@@request_mutex = Mutex.new
def self.start_request
@@request_mutex.synchronize do
@@active_requests += 1
end
end
def self.end_request
@@request_mutex.synchronize do
@@active_requests -= 1
end
end
def self.active_count
@@request_mutex.synchronize { @@active_requests }
end
def process
self.class.start_request
begin
yield if block_given?
ensure
self.class.end_request
end
end
end
class UserService < BaseService
def handle_user_request(user_id)
process do
# User-specific processing
sleep(rand * 0.1)
end
end
end
class OrderService < BaseService
def handle_order_request(order_id)
process do
# Order-specific processing
sleep(rand * 0.1)
end
end
end
# Both services share the same counter and mutex
user_threads = 10.times.map do
Thread.new do
service = UserService.new
5.times { service.handle_user_request(rand(1000)) }
end
end
order_threads = 10.times.map do
Thread.new do
service = OrderService.new
5.times { service.handle_order_request(rand(1000)) }
end
end
(user_threads + order_threads).each(&:join)
puts "Final active count: #{BaseService.active_count}" # Should be 0
Common Pitfalls
Class variable inheritance creates unexpected sharing behaviors where subclass modifications affect parent classes and sibling classes. Developers often expect subclasses to have independent class variables, but Ruby shares the same storage location across the entire hierarchy.
class Animal
@@sound = "generic sound"
def self.sound
@@sound
end
def self.sound=(new_sound)
@@sound = new_sound
end
def speak
puts @@sound
end
end
class Dog < Animal
@@sound = "woof" # This modifies Animal's @@sound!
end
class Cat < Animal
@@sound = "meow" # This also modifies Animal's @@sound!
end
# Unexpected behavior:
Animal.sound # => "meow" (not "generic sound")
Dog.sound # => "meow" (not "woof")
Cat.sound # => "meow"
dog = Dog.new
dog.speak # => "meow" (not "woof")
# The last assignment wins for all classes
Class variable scope differs from constant scope, leading to confusion when mixing these mechanisms. Constants create separate copies in subclasses, while class variables share storage. This difference causes inconsistent behavior when refactoring between constants and class variables.
class Configuration
# Using constants - each class gets its own copy
DEFAULT_TIMEOUT = 30
# Using class variables - shared across hierarchy
@@current_timeout = 30
def self.timeout
@@current_timeout
end
def self.timeout=(value)
@@current_timeout = value
end
end
class WebConfiguration < Configuration
DEFAULT_TIMEOUT = 60 # Independent constant
@@current_timeout = 60 # Modifies parent's class variable
end
class ApiConfiguration < Configuration
DEFAULT_TIMEOUT = 10 # Independent constant
@@current_timeout = 10 # Modifies parent's class variable
end
# Constants behave as expected:
Configuration::DEFAULT_TIMEOUT # => 30
WebConfiguration::DEFAULT_TIMEOUT # => 60
ApiConfiguration::DEFAULT_TIMEOUT # => 10
# Class variables share state unexpectedly:
Configuration.timeout # => 10 (modified by ApiConfiguration)
WebConfiguration.timeout # => 10 (shares same variable)
ApiConfiguration.timeout # => 10
Module inclusion creates additional sharing complexities when modules define class variables. Multiple classes including the same module share the module's class variables, creating hidden coupling between unrelated classes.
module Cacheable
def self.included(base)
base.class_eval do
@@cache = {}
end
end
def cache_get(key)
@@cache[key]
end
def cache_set(key, value)
@@cache[key] = value
end
def cache_size
@@cache.size
end
end
class User
include Cacheable
def initialize(name)
@name = name
cache_set("user_#{name}", self)
end
end
class Product
include Cacheable
def initialize(sku)
@sku = sku
cache_set("product_#{sku}", self)
end
end
# Unexpected sharing between unrelated classes:
user = User.new("john")
user.cache_size # => 1
product = Product.new("ABC123")
product.cache_size # => 2 (shares cache with User!)
user.cache_size # => 2 (now includes product data)
# User cache contains product data:
user.cache_get("product_ABC123") # => #<Product:...>
Class variable initialization timing can cause errors when accessed before definition. Unlike instance variables that return nil
when uninitialized, class variables raise NameError
exceptions. This behavior creates order-dependency issues in class definitions.
class ProblematicService
def self.configure(options)
@@options = options # First assignment initializes the variable
end
def self.get_option(key)
@@options[key] # NameError if called before configure
end
def initialize
# This will fail if configure hasn't been called:
@timeout = @@options[:timeout]
end
end
# This raises NameError:
begin
ProblematicService.get_option(:timeout)
rescue NameError => e
puts e.message # => "uninitialized class variable @@options"
end
# Safer approach with initialization guard:
class SaferService
@@options = {} # Initialize immediately
def self.configure(options)
@@options.merge!(options)
end
def self.get_option(key)
@@options[key]
end
def initialize
@timeout = @@options.fetch(:timeout, 30) # Provide default
end
end
Reference
Class Variable Syntax
Syntax | Purpose | Example |
---|---|---|
@@variable |
Declare/access class variable | @@count = 0 |
@@variable = value |
Assignment | @@name = "default" |
defined?(@@variable) |
Check existence | defined?(@@count) |
self.class.class_variables |
List all class variables | Returns array of symbols |
Core Methods
Method | Parameters | Returns | Description |
---|---|---|---|
class_variable_defined?(symbol) |
symbol (Symbol/String) |
Boolean |
Check if class variable exists |
class_variable_get(symbol) |
symbol (Symbol/String) |
Object |
Get class variable value |
class_variable_set(symbol, value) |
symbol (Symbol/String), value (Object) |
Object |
Set class variable value |
class_variables(inherit=true) |
inherit (Boolean) |
Array |
List class variable names |
remove_class_variable(symbol) |
symbol (Symbol/String) |
Object |
Remove class variable |
Inheritance Behavior
Scenario | Behavior | Example Result |
---|---|---|
Parent defines @@var | Child accesses same storage | Shared value |
Child modifies @@var | Parent sees modification | Modified value |
Multiple children modify @@var | Last modification wins | Latest value |
Module defines @@var | All including classes share | Shared storage |
Scope Access Rules
Context | Access | Modification | Notes |
---|---|---|---|
Class body | Read/Write | Yes | Direct access during definition |
Class method | Read/Write | Yes | Use @@variable syntax |
Instance method | Read/Write | Yes | Use @@variable syntax |
Subclass | Read/Write | Yes | Shares parent's storage |
Outside class | No | No | Private to class hierarchy |
Common Error Types
Error | Cause | Solution |
---|---|---|
NameError |
Accessing uninitialized class variable | Initialize in class body |
TypeError |
Wrong symbol type for metaprogramming methods | Use Symbol or String |
Data corruption | Concurrent access without synchronization | Use Mutex or atomic operations |
Unexpected sharing | Subclass modifications affect parents | Use class instance variables instead |
Thread Safety Considerations
Operation | Thread Safe | Recommendation |
---|---|---|
Simple assignment | Yes | @@var = value |
Read access | Yes | @@var |
Read-modify-write | No | Use Mutex.synchronize |
Hash/Array modification | No | Synchronize entire operation |
Conditional assignment | No | Use Mutex for atomicity |
Alternative Patterns
Pattern | Use Case | Implementation |
---|---|---|
Class instance variables | Independent subclass state | @var in class body |
Constants | Immutable configuration | CONSTANT = value |
Module configuration | Shared settings | Module with accessor methods |
Singleton pattern | Global state | Class with instance management |