読者です 読者をやめる 読者になる 読者になる

Rack::Responseオブジェクトを多重代入すると、暗黙的にfinishが呼ばれる

環境:ruby-1.9.2、rack-1.4.1

RAILSCASTのRackの章を見ていたら、以下のような構文が出てきて、不思議に思った。

def call(env)
  Rack::Response.new("hoge")
end

Rackアプリの呼び出し側(例えば、lintミドルウェア)では、このように書かれている

status, headers, @body = @app.call(env)

上記の場合、@app.call(env)を評価した結果はRack::Responseオブジェクトであるはず。なのに、それを多重代入できるのはなぜか?配列じゃないといけないのでは?

多重代入の仕様が怪しいので、調べる。

4.5.5.3 複数の左辺、1 個の配列の右辺

左辺は複数あるが、右辺は 1 つしかない場合、Ruby はその右辺を代入すべき値のリストに展開しようとす
る。右辺が配列なら、Rubyは配列を展開し、個々の要素が独自の右辺になるようにする。右辺が配列でなくても、to_aryメソッドを実装する場合は、to_aryを呼び出して配列を作ってから、その配列を展開して代入する。

プログラミング言語 Ruby P102

どうやら、上記のような状況下で多重代入を行うと、右辺のオブジェクトにto_aryメソッドがある場合、その実行が行われるようだ。ということは、おそらくRack::Responseクラスのインスタンスメソッドにto_aryが定義されているはず。Rackのソースコードを見てみる。

# rack-1.4.1/lib/rack/response.rb
def finish(&block)                                                                                                                                                                                          
  @block = block

  if [204, 205, 304].include?(status.to_i)
    header.delete "Content-Type"
    header.delete "Content-Length"
    [status.to_i, header, []] 
  else
    [status.to_i, header, self]
  end 
end 
alias to_a finish           # For *response
alias to_ary finish         # For implicit-splat on Ruby 1.9.2

to_aryメソッドはfinishのエイリアスになっていた!なので、Rack::Responseオブジェクトが暗黙的に多重代入されると、finishメソッドの実行結果が代入されるということになる。なるほどー!

※追記
Rubyの多重代入におけるto_aとto_aryの挙動について書いた