LaunchKit
All quizzes
Ruby 0 views

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

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
  1. Passing a block to a method is always legal; whether it runs is up to the method. method1 executes it via yield; method2 accepts the block and does nothing with it. No error, no warning, no B.
  2. When the first block runs, A = :file1 executes. 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 defines Object::A, visible everywhere.
  3. [A, B] resolves A to :file1, then fails on B with NameError.

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.

Only used to confirm and publish your comment. Never shown publicly, never shared.

Markdown: **bold**, `code`, ```fenced blocks```, > quotes, [links](url). HTML and images are not rendered.