Overview
Global variables in Ruby are variables prefixed with the dollar sign ($) that maintain accessibility throughout the entire program execution. Ruby creates a global namespace where these variables persist across all scopes, classes, methods, and modules without requiring explicit passing or declaration.
Ruby implements global variables through its internal variable table, maintaining references that survive garbage collection cycles. The interpreter initializes several predefined global variables during startup and allows runtime creation of custom globals through assignment operations.
$custom_global = "available everywhere"
class MyClass
def access_global
puts $custom_global # Accessible without declaration
end
end
def standalone_method
$custom_global = "modified from function"
end
MyClass.new.access_global # => "available everywhere"
standalone_method
MyClass.new.access_global # => "modified from function"
Ruby's predefined globals include process information ($$
, $0
), regular expression matches ($1
, $2
, $&
), input/output streams ($stdin
, $stdout
, $stderr
), and interpreter state ($LOAD_PATH
, $LOADED_FEATURES
). These variables provide system-level access and maintain state between operations.
puts $$ # Process ID
puts $0 # Script name
"hello" =~ /(l+)/
puts $1 # => "ll" (first capture group)
puts $LOAD_PATH # Array of require paths
Global variables accept any Ruby object as values, including complex data structures, and maintain object identity across assignments. Ruby performs no automatic type conversion or validation on global variable assignments.
Basic Usage
Global variable assignment follows standard Ruby syntax with the dollar prefix. Ruby creates the variable immediately upon first assignment, making it accessible throughout the program lifetime.
$user_count = 0
$configuration = { database: "postgresql", timeout: 30 }
$error_log = []
def increment_users
$user_count += 1
end
def log_error(message)
$error_log << { timestamp: Time.now, message: message }
end
increment_users
increment_users
log_error("Database connection failed")
puts $user_count # => 2
puts $error_log.size # => 1
Reading undefined global variables returns nil
without raising exceptions. This behavior differs from local and instance variables, which raise NameError
when accessed before definition.
puts $undefined_variable # => nil (no error)
puts undefined_local # NameError: undefined local variable
Global variables maintain object references, not copies. Modifying mutable objects affects all references to that global variable throughout the program.
$shared_data = [1, 2, 3]
class DataProcessor
def add_item(item)
$shared_data << item
end
end
class DataDisplay
def show_all
puts $shared_data.inspect
end
end
processor = DataProcessor.new
display = DataDisplay.new
processor.add_item(4)
display.show_all # => [1, 2, 3, 4]
Ruby provides several methods for global variable introspection. The global_variables
method returns an array of all defined global variable symbols, while defined?
checks for variable existence.
$example = "test"
puts global_variables.include?(:$example) # => true
puts defined?($example) # => "global-variable"
puts defined?($nonexistent) # => nil
Common Pitfalls
Global variables create invisible dependencies between code sections, making programs difficult to test and debug. Methods that rely on global variables become coupled to external state, violating encapsulation principles and creating unpredictable behavior.
$debug_mode = true
class Calculator
def add(a, b)
result = a + b
puts "Adding #{a} + #{b} = #{result}" if $debug_mode
result
end
end
# Testing becomes problematic - test behavior depends on global state
calc = Calculator.new
calc.add(2, 3) # Output varies based on $debug_mode value
# Tests can interfere with each other
$debug_mode = false # This affects all subsequent tests
Concurrent access to global variables creates race conditions without proper synchronization. Multiple threads modifying globals simultaneously produce inconsistent results and data corruption.
$counter = 0
threads = 10.times.map do
Thread.new do
1000.times { $counter += 1 } # Race condition
end
end
threads.each(&:join)
puts $counter # Result varies, often less than 10000
Variable naming conflicts occur when libraries or gems define globals with identical names. Ruby overwrites existing globals without warnings, causing mysterious bugs when dependencies clash.
# Library A defines
$config = { mode: "development" }
# Library B unknowingly overwrites
$config = { timeout: 30, retries: 3 }
# Library A's code now fails
puts $config[:mode] # => nil (expected "development")
Global variables persist throughout program execution, preventing garbage collection of referenced objects. Large objects stored in globals consume memory indefinitely, creating memory leaks in long-running applications.
$large_dataset = File.read("huge_file.csv").split("\n").map { |line| line.split(",") }
def process_small_subset
# Only need first 10 rows, but entire dataset stays in memory
$large_dataset.first(10).each { |row| puts row.join("|") }
end
# $large_dataset never gets garbage collected
Method parameter shadowing creates confusion when local parameters have similar names to globals. Ruby prioritizes local scope, making global access impossible within those methods without explicit syntax.
$data = "global value"
def process_data(data)
puts data # Prints parameter, not global
puts $data # Must use $ prefix to access global
end
process_data("local value")
# => "local value"
# => "global value"
Error Handling & Debugging
Global variable debugging requires systematic state tracking across program execution. Ruby's global variable inspection tools help identify state changes and unexpected modifications.
module GlobalDebugger
def self.track_global(var_name)
original_value = eval("$#{var_name}")
trace = TracePoint.new(:line) do |tp|
current_value = eval("$#{var_name}")
if current_value != original_value
puts "Global $#{var_name} changed in #{tp.path}:#{tp.lineno}"
puts " From: #{original_value.inspect}"
puts " To: #{current_value.inspect}"
original_value = current_value
end
end
trace.enable
yield
trace.disable
end
end
# Usage
$tracked_var = "initial"
GlobalDebugger.track_global("tracked_var") do
$tracked_var = "modified"
some_complex_operation
$tracked_var = "final"
end
Thread safety debugging requires monitoring concurrent access patterns. Custom synchronization wrappers help identify race conditions and provide safe access mechanisms.
class ThreadSafeGlobal
def initialize(var_name, initial_value = nil)
@var_name = var_name
@mutex = Mutex.new
@access_log = []
eval("$#{var_name} = initial_value")
end
def get
@mutex.synchronize do
log_access(:read)
eval("$#{@var_name}")
end
end
def set(value)
@mutex.synchronize do
log_access(:write, value)
eval("$#{@var_name} = value")
end
end
private
def log_access(type, value = nil)
@access_log << {
thread: Thread.current.object_id,
type: type,
value: value,
timestamp: Time.now
}
end
def access_log
@access_log
end
end
# Usage
$safe_counter = ThreadSafeGlobal.new("counter", 0)
threads = 5.times.map do
Thread.new do
10.times do
current = $safe_counter.get
$safe_counter.set(current + 1)
end
end
end
threads.each(&:join)
puts $safe_counter.get # => 50 (guaranteed)
Global variable validation prevents invalid assignments and maintains data integrity. Custom setter methods provide type checking and constraint enforcement.
module GlobalValidation
def self.define_validated_global(name, validator)
var_name = "$#{name}"
# Create getter method
define_singleton_method("get_#{name}") do
eval(var_name)
end
# Create setter method with validation
define_singleton_method("set_#{name}") do |value|
unless validator.call(value)
raise ArgumentError, "Invalid value for #{var_name}: #{value.inspect}"
end
eval("#{var_name} = value")
end
end
end
# Define validated globals
GlobalValidation.define_validated_global("user_count", ->(v) { v.is_a?(Integer) && v >= 0 })
GlobalValidation.define_validated_global("config_mode", ->(v) { ["dev", "test", "prod"].include?(v) })
# Safe usage
GlobalValidation.set_user_count(42)
GlobalValidation.set_config_mode("dev")
# Raises ArgumentError
begin
GlobalValidation.set_user_count(-5)
rescue ArgumentError => e
puts e.message # => "Invalid value for $user_count: -5"
end
Performance & Memory
Global variable access performance remains constant regardless of scope depth, unlike local variable lookups that traverse scope chains. Ruby's variable table provides O(1) access time for global variables through hash table implementation.
require 'benchmark'
$global_var = "test"
local_var = "test"
def deep_method_chain(depth)
return $global_var if depth == 0
local_var = "local"
deep_method_chain(depth - 1)
end
# Benchmark global vs local access
Benchmark.bm(15) do |x|
x.report("global access:") do
1_000_000.times { $global_var }
end
x.report("local access:") do
1_000_000.times { local_var }
end
x.report("deep global:") do
100_000.times { deep_method_chain(10) }
end
end
Memory consumption patterns differ significantly between global and local variables. Global variables prevent garbage collection of referenced objects, while local variables allow automatic cleanup when leaving scope.
class MemoryTracker
def self.measure_memory
GC.start
ObjectSpace.count_objects[:TOTAL] - ObjectSpace.count_objects[:FREE]
end
def self.test_global_memory
initial = measure_memory
$large_array = Array.new(100_000) { |i| "string_#{i}" }
after_assignment = measure_memory
$large_array = nil
GC.start
after_nil = measure_memory
{
initial: initial,
after_assignment: after_assignment,
after_nil: after_nil,
memory_freed: after_assignment - after_nil
}
end
def self.test_local_memory
initial = measure_memory
def create_local_array
large_array = Array.new(100_000) { |i| "string_#{i}" }
# Array goes out of scope here
end
create_local_array
GC.start
after_method = measure_memory
{
initial: initial,
after_method: after_method,
memory_difference: after_method - initial
}
end
end
puts MemoryTracker.test_global_memory
puts MemoryTracker.test_local_memory
Global variable lookup optimization involves minimizing access frequency and caching values locally when performing repeated operations.
# Inefficient: repeated global access
def process_items_slow(items)
items.map do |item|
if $debug_mode
puts "Processing: #{item}"
end
item.upcase
end
end
# Optimized: cache global value
def process_items_fast(items)
debug = $debug_mode # Cache global value
items.map do |item|
if debug
puts "Processing: #{item}"
end
item.upcase
end
end
# Benchmark difference
require 'benchmark'
$debug_mode = true
items = Array.new(10_000) { |i| "item_#{i}" }
Benchmark.bm(20) do |x|
x.report("repeated global:") { process_items_slow(items) }
x.report("cached global:") { process_items_fast(items) }
end
Reference
Global Variable Syntax
Syntax | Description | Example |
---|---|---|
$variable |
Define/access global variable | $count = 42 |
$variable = value |
Assignment | $config = {} |
defined?($variable) |
Check existence | defined?($count) |
global_variables |
List all globals | global_variables.grep(/count/) |
Predefined Global Variables
Variable | Type | Description |
---|---|---|
$$ |
Integer | Current process ID |
$0 |
String | Script name (program name) |
$* |
Array | Command line arguments (ARGV) |
$. |
Integer | Current input line number |
$/ |
String | Input record separator |
$\ |
String | Output record separator |
$, |
String | Output field separator |
$; |
String | Default separator for String#split |
$< |
Object | Virtual concatenation of ARGV files |
$> |
Object | Default output for print, printf |
$_ |
String | Last line read by gets |
$& |
String | Last successful pattern match |
$` |
String | String preceding last match |
$' |
String | String following last match |
$+ |
String | Last parenthesized group matched |
$1 , $2 , ... |
String | Nth group of last match |
$~ |
MatchData | Information about last match |
$= |
Boolean | Case-insensitive flag (deprecated) |
$! |
Exception | Current exception |
$@ |
Array | Backtrace of current exception |
$SAFE |
Integer | Security level (deprecated) |
$stdin |
IO | Standard input |
$stdout |
IO | Standard output |
$stderr |
IO | Standard error |
$DEBUG |
Boolean | Debug flag (-d option) |
$FILENAME |
String | Current filename from $< |
$LOAD_PATH |
Array | Load path for scripts and libraries |
$LOADED_FEATURES |
Array | Filenames loaded via require |
$PROGRAM_NAME |
String | Same as $0 |
$VERBOSE |
Boolean | Verbose flag (-v option) |
Regular Expression Globals
Variable | Set By | Description |
---|---|---|
$~ |
=~ , match |
MatchData object |
$& |
=~ , match |
Entire matched string |
$` |
=~ , match |
String before match |
$' |
=~ , match |
String after match |
$+ |
=~ , match |
Last grouped match |
$1 -$9 |
=~ , match |
Nth grouped match |
Global Variable Methods
Method | Returns | Description |
---|---|---|
global_variables |
Array | All global variable symbols |
defined?($var) |
String/nil | "global-variable" or nil |
eval("$var") |
Object | Dynamic global access |
eval("$var = value") |
Object | Dynamic global assignment |
remove_instance_variable |
N/A | Cannot remove globals |
Common Patterns
# Configuration globals
$app_config = {
environment: ENV['RAILS_ENV'] || 'development',
database_url: ENV['DATABASE_URL'],
redis_url: ENV['REDIS_URL']
}
# Debugging globals
$debug_queries = ENV['DEBUG_QUERIES'] == 'true'
$log_level = ENV['LOG_LEVEL'] || 'info'
# State tracking
$request_id = nil
$current_user = nil
# Feature flags
$feature_flags = {
new_ui: true,
advanced_search: false,
beta_feature: ENV['BETA'] == 'true'
}
# Error tracking
$error_count = 0
$last_error = nil
$error_callback = ->(error) { puts "Error: #{error.message}" }
Thread Safety Considerations
Pattern | Safety Level | Use Case |
---|---|---|
Read-only globals | Safe | Configuration, constants |
Write-once globals | Safe | Initialization values |
Mutex-protected writes | Safe | Shared counters, state |
Atomic operations | Safe | Simple numeric operations |
Unprotected writes | Unsafe | Concurrent modifications |
Complex object mutations | Unsafe | Hash/Array modifications |