Skip to content
Closures and Bindings
step 1/7

Reading — step 1 of 7

Learn

~3 min readClosures, Fibers, Refinements

Ruby's closures (Procs, Lambdas, blocks) capture not just outer variables — they capture the entire binding (the local scope at definition time). Metaprogramming Ruby spends a chapter on binding tricks alone.

Proc vs Lambda — the core difference

p_proc = Proc.new { |x, y| x + (y || 0) }
p_proc.call(1)         # 1 — missing arg becomes nil
p_proc.call(1, 2, 3)   # 3 — extra arg dropped

l_lambda = lambda { |x, y| x + y }
# l_lambda.call(1)     # ArgumentError — strict arity
l_lambda.call(1, 2)    # 3

Lambdas are STRICT about arity. Procs are loose. The arrow syntax ->() makes lambdas:

add = ->(a, b) { a + b }
add.call(2, 3)        # 5
add.(2, 3)            # 5 — alternative syntax
add[2, 3]             # 5 — bracket syntax

return semantics — the deeper difference

def proc_test
  p = Proc.new { return 1 }
  p.call                     # return jumps OUT of proc_test!
  return 2                   # never reached
end
puts proc_test               # 1

def lambda_test
  l = lambda { return 1 }
  l.call                     # return only exits the lambda
  return 2
end
puts lambda_test             # 2

In a Proc, return returns from the ENCLOSING method. In a lambda, it returns from the lambda only. This is the most important practical difference.

Capturing bindings

Closures see local variables defined in their enclosing scope:

def counter
  count = 0
  -> { count += 1 }      # closure captures `count` by reference
end

c = counter
c.call    # 1
c.call    # 2
c.call    # 3

Each call to counter creates a fresh count and a closure capturing it. Different from Java's effectively-final captures — Ruby captures by reference, mutations persist.

Binding objects

binding (a method) returns a Binding object representing the current scope:

def trap
  x = 10
  binding
end

b = trap
b.eval("x")       # 10 — accessing the captured local
b.eval("x = 999")
b.eval("x")       # 999

Useful for debugging: drop a binding.irb (Ruby 2.5+) into your code to start an interactive REPL with all locals visible.

instance_eval — switching self

Run a block in the context of a different object:

class Person
  def initialize(name)
    @name = name
  end
end

p = Person.new("Ada")
p.instance_eval { puts @name }     # "Ada" — accesses @name as if inside p
p.instance_eval { @age = 36 }      # adds an instance variable to p

Gives the block access to the object's privates. Used by every DSL — RSpec, Sinatra, Rake.

class_eval — adding methods at runtime

class User
  attr_accessor :name
end

User.class_eval do
  def shout
    @name.upcase
  end
end

User.new.tap { |u| u.name = "ada"; puts u.shout }   # "ADA"

Reopens the class to add methods. Lower-level than define_method — use class_eval do def ... end for clean syntax.

Common mistakes

  • Using a Proc when a lambda is needed — return semantics surprise.
  • Captured variables in loops — closures capture by reference; old loop iteration variables are shared. Use a local re-binding per iteration.
  • Mixing Proc.new and procproc (lowercase) is a Proc.new. Just consistent style — pick one.
  • instance_eval blocks losing local variables — block has access to the OBJECT's state; the OUTER scope's locals are still available, but self switches.
  • Trying to use return in a top-level Proc — LocalJumpError, since there's no enclosing method to return from.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…