🐈

Ruby で Block を使うときに変数がキャプチャーされていないときは、instance_evalを疑おう

2021/05/30に公開

tl;dr

Ruby で Block を使うときに変数がキャプチャーされていないときは、instance_evalを疑おう。というか実装を読もう。

くわしく

今日Rack::Builderを触っていて、下記のコードが予想と違う挙動をしたことが気になった。

require 'rack'
@something = 'something'
Rack::Builder.new do
  puts @something
end

これはnilとなり、空行が出力される。
「はて、block(clousure)なのに何で変数キャプチャーされてないの?」と思ったら、
実装が下記だった。

def initialize(default_app = nil, &block)
  @use, @map, @run, @warmup, @freeze_app = [], nil, default_app, nil, false
  instance_eval(&block) if block_given?
end

ref

instance_eval がつくられたばかりのオブジェクトに対して呼ばれるので、block宣言時の変数はキャプチャーされていないのであった。

(補足)
自分で上記の説明読み返してみても不明だったので。
上記コードで、instance_evalのレシーバーはRack::Builderのインスタンス。単純に block.call をした場合と違って、この時点で .new を呼ばれたスコープへのアクセスは消えている状態。

参考

https://rubymonk.com/learning/books/5-metaprogramming-ruby-ascent/chapters/24-eval/lessons/67-instance-eval

Discussion