Overview
Variable scope determines where variables can be accessed within a Ruby program. Ruby uses lexical scoping combined with dynamic binding to create predictable yet flexible variable access patterns. The language defines four primary variable types based on their scope and naming conventions: local variables, instance variables, class variables, and global variables.
Ruby's Binding
class captures the execution context at a specific point, preserving variable scope and method definitions. This binding object contains references to local variables, instance variables, and the current self
object. The binding mechanism enables metaprogramming techniques like eval
, instance_eval
, and class_eval
to execute code within captured contexts.
def capture_context
local_var = "captured"
binding
end
context = capture_context
context.eval("local_var") # => "captured"
Local variables exist within method definitions, blocks, and class/module bodies. Their scope follows lexical boundaries, ending at method returns or block termination. Instance variables belong to specific object instances and remain accessible throughout the object's lifetime. Class variables are shared among all instances of a class and its subclasses, while global variables maintain program-wide accessibility.
class ScopeExample
@@class_var = "shared"
def initialize
@instance_var = "per object"
end
def method_scope
local_var = "method only"
[local_var, @instance_var, @@class_var]
end
end
Ruby creates new binding objects automatically when entering methods, blocks, or eval contexts. These bindings maintain references to variables accessible at creation time, forming closures that preserve state even after their lexical scope ends.
Basic Usage
Local variables use lowercase or underscore-prefixed names and exist within their defining scope. Ruby determines local variable scope at parse time, creating variables when assignments appear in the source code regardless of execution flow.
def variable_scope_demo
if false
unreachable = "never executed"
end
puts unreachable # => nil (variable exists, value is nil)
end
Method parameters create local variables within the method scope. Block parameters create new local variables that shadow outer variables with the same name within the block.
outer = "original"
[1, 2, 3].each do |outer|
puts outer # => 1, 2, 3 (shadows outer variable)
end
puts outer # => "original" (unchanged)
Instance variables begin with @
and belong to the current object referenced by self
. Ruby creates instance variables on first assignment and returns nil
for undefined instance variables.
class InstanceVariableExample
def set_var
@name = "Ruby"
end
def get_var
@name # Returns value or nil
end
def undefined_var
@undefined # => nil
end
end
Class variables start with @@
and are shared across all instances of a class and its subclasses. Ruby raises NameError
when accessing undefined class variables.
class Counter
@@count = 0
def increment
@@count += 1
end
def current_count
@@count
end
end
c1, c2 = Counter.new, Counter.new
c1.increment
c2.current_count # => 1
The binding
method returns a Binding
object containing the current execution context. This object preserves local variables, instance variables, and the current self
reference.
def create_binding
name = "context"
age = 25
binding
end
context = create_binding
context.local_variables # => [:name, :age]
context.eval("name") # => "context"
Advanced Usage
Blocks create closures that capture variables from their defining scope. These closures maintain references to outer variables even after the defining method returns, enabling sophisticated functional programming patterns.
def create_counter(initial = 0)
count = initial
-> { count += 1 }
end
counter1 = create_counter(10)
counter2 = create_counter
counter1.call # => 11
counter1.call # => 12
counter2.call # => 1
Binding objects support method chaining for context evaluation. The eval
method executes strings within the binding's context, while local_variable_get
and local_variable_set
provide direct variable access.
def advanced_binding_demo
secret = "hidden value"
public_data = "visible"
context = binding
context.local_variable_set(:secret, "modified")
context.eval("secret + ' and ' + public_data")
end
advanced_binding_demo # => "modified and visible"
Ruby's scope gates (class, module, and def keywords) create new variable scopes that cannot access local variables from outer scopes. This behavior differs from block scoping where outer variables remain accessible.
local_var = "outer"
class ScopeGate
# local_var is not accessible here
def method_definition
# local_var is not accessible here
inner_var = "method scope"
end
[1, 2, 3].each do |n|
# local_var IS accessible in blocks
puts "#{local_var}: #{n}"
end
end
The Proc
and lambda
objects capture their creation context as bindings. This capture includes local variables, creating true closures with persistent state.
class ClosureFactory
def create_proc(multiplier)
base = 10
Proc.new do |value|
result = base * multiplier * value
base += 1 # Modifies captured variable
result
end
end
end
factory = ClosureFactory.new
proc_obj = factory.create_proc(3)
proc_obj.call(2) # => 60 (10 * 3 * 2, base becomes 11)
proc_obj.call(2) # => 66 (11 * 3 * 2, base becomes 12)
Method objects created with Object#method
capture the receiver's binding, preserving instance variable access even when called from different contexts.
class BindingPreservation
def initialize(value)
@value = value
end
def get_value
@value
end
def modify_value(new_value)
@value = new_value
end
end
obj = BindingPreservation.new("original")
getter = obj.method(:get_value)
setter = obj.method(:modify_value)
# Methods preserve original object binding
setter.call("modified")
getter.call # => "modified"
Common Pitfalls
Variable shadowing occurs when inner scopes define variables with the same names as outer variables. Ruby prioritizes the innermost scope, potentially hiding intended variables.
name = "outer"
def confusing_shadowing
name = "method"
[1, 2, 3].each do |item|
name = "block #{item}"
puts name
end
puts name # => "block 3" (method variable was overwritten)
end
Local variable creation happens at parse time, not execution time. Ruby creates local variable slots for any assignment statement it encounters, even within unreachable code paths.
def parse_time_confusion
puts defined?(variable) # => "local-variable"
if false
variable = "never assigned"
end
puts variable # => nil (exists but uninitialized)
end
Class variable inheritance creates unexpected sharing between parent and child classes. Modifications in subclasses affect the parent class and all other subclasses.
class Parent
@@shared = "parent value"
def self.get_shared
@@shared
end
def self.set_shared(value)
@@shared = value
end
end
class Child < Parent
end
Child.set_shared("modified by child")
Parent.get_shared # => "modified by child"
Block variable scoping with parameters creates new variables that shadow outer variables, but block-local variables (Ruby 1.9+) can prevent this shadowing.
outer = "preserved"
# This shadows the outer variable
[1].each do |outer|
outer = "modified"
end
puts outer # => "preserved"
# Block-local variables prevent confusion
[1].each do |item; outer|
outer = "block local"
puts outer # => "block local"
end
puts outer # => "preserved"
Binding objects maintain references to their creation context, potentially causing memory leaks if large objects remain referenced through captured variables.
def memory_leak_example
large_data = Array.new(1_000_000) { rand }
# This binding keeps large_data alive
-> { "closure created" }
end
# large_data cannot be garbage collected
closures = Array.new(100) { memory_leak_example }
The eval
method within bindings can access and modify captured variables, but string evaluation bypasses Ruby's normal variable scope checking.
def dangerous_eval
password = "secret123"
user_input = "password = 'hacked'; password"
context = binding
context.eval(user_input) # => "hacked" (password was modified)
end
Constants have lexical scope but follow different resolution rules than variables. Ruby searches constant names through the inheritance hierarchy and lexical nesting.
module Outer
CONSTANT = "outer"
module Inner
CONSTANT = "inner"
def self.access_constant
CONSTANT # => "inner" (lexical scope)
end
def self.access_outer
Outer::CONSTANT # => "outer" (explicit qualification)
end
end
end
Reference
Variable Types and Scope Rules
Variable Type | Syntax | Scope | Inheritance | Access Pattern |
---|---|---|---|---|
Local | variable |
Lexical block | No | Direct name |
Instance | @variable |
Object instance | No | Through object |
Class | @@variable |
Class hierarchy | Yes | Shared across instances |
Global | $variable |
Program-wide | N/A | Global access |
Binding Class Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#eval(string, filename=nil, lineno=1) |
string (String), optional location |
Object |
Evaluates string in binding context |
#local_variables |
None | Array<Symbol> |
Returns local variable names |
#local_variable_get(symbol) |
symbol (Symbol) |
Object |
Gets local variable value |
#local_variable_set(symbol, obj) |
symbol (Symbol), obj (Object) |
Object |
Sets local variable value |
#local_variable_defined?(symbol) |
symbol (Symbol) |
Boolean |
Checks if local variable exists |
#receiver |
None | Object |
Returns the bound object (self) |
Scope Gate Keywords
Keyword | Creates New Scope | Access to Outer Variables | Usage Context |
---|---|---|---|
class |
Yes | No | Class definition |
module |
Yes | No | Module definition |
def |
Yes | No | Method definition |
lambda |
No (closure) | Yes | Lambda creation |
proc |
No (closure) | Yes | Proc creation |
Block | No (closure) | Yes | Block execution |
Variable Resolution Order
- Local variables - Current scope, then outer lexical scopes (blocks only)
- Method calls - Current object, then method lookup chain
- Constants - Lexical scope, then inheritance hierarchy, then top-level
Binding Creation Points
Context | Binding Contains | Variable Access |
---|---|---|
Method call | Method parameters, local variables, self |
Instance variables, class variables |
Block execution | Block parameters, outer variables, self |
Closure over creation scope |
eval execution |
String evaluation context | Context-dependent access |
Class/Module body | Class-level context, constants | Class instance variables |
Common Binding Patterns
# Capture current context
context = binding
# Evaluate string in context
context.eval("local_variable")
# Access binding variables
context.local_variable_get(:name)
context.local_variable_set(:name, "new value")
# Check variable existence
context.local_variable_defined?(:name)
Variable Naming Conventions
Pattern | Variable Type | Example | Scope |
---|---|---|---|
[a-z_][a-zA-Z0-9_]* |
Local | user_name |
Lexical |
@[a-zA-Z_][a-zA-Z0-9_]* |
Instance | @name |
Object |
@@[a-zA-Z_][a-zA-Z0-9_]* |
Class | @@count |
Class hierarchy |
$[a-zA-Z_][a-zA-Z0-9_]* |
Global | $global_var |
Program |
[A-Z][A-Z0-9_]* |
Constant | CONSTANT |
Lexical + inheritance |