CrackedRuby logo

CrackedRuby

Global Variables ($variable)

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