Constants assigned inside blocks leak out
Question
What is the return value of the following Ruby 3 code?
def method1
yield
end
def method2
end
method1 { A = :file1 }
method2 { B = :file2 }
[A, B] # => ???
The correct answer is
-
uninitialized constant
A -
uninitialized constant
BCorrect -
[:file1, nil] -
[nil, :file2]
Explanation
TL;DR
method1 yields, so its block runs and the assignment A = :file1 defines the constant A. method2 never calls yield, so its block is silently ignored and B is never defined. Evaluating [A, B] then raises NameError: uninitialized constant B.
Step by step
def method1
yield
end
def method2
end
method1 { A = :file1 }
method2 { B = :file2 }
A # => :file1
defined?(B) # => nil
begin
[A, B]
rescue NameError => e
e.message # => "uninitialized constant B"
end
- Passing a block to a method is always legal; whether it runs is up to the method.
method1executes it viayield;method2accepts the block and does nothing with it. No error, no warning, noB. - When the first block runs,
A = :file1executes. Blocks scope local variables, but they do not scope constants: a constant assignment inside a block is attached to the lexical scope where the block's code is written. Here that is the top level, so the assignment definesObject::A, visible everywhere. [A, B]resolvesAto:file1, then fails onBwithNameError.
Edge cases
The block is essential to this trick. Inside a method body, Ruby rejects constant assignment at parse time, before anything runs (the eval here is only to catch the parse error):
begin
eval("def m\n X = 1\nend")
rescue SyntaxError => e
e.message.include?("dynamic constant assignment") # => true
end
Blocks escape that rule because they may execute in a context where the assignment is legitimate (a class_eval body, for instance), so the parser lets them through, and a top-level block quietly pollutes Object with globals. If a value belongs to the block, use a local variable. If a method wants to know whether it received a block before yielding, it checks block_given?.
Share this quiz
Comments
No comments yet. Be the first.