Techioz Blog

これは Ruby のスローで予期される動作ですか?

概要

私はトラップやキャッチ/スローメソッドを使用して、それらがどのように使用できるか、使用されるべきかをよりよく理解しているときにこの動作に気づきました。

Ruby では、catch メソッドと throw メソッドはペアで使用することを意図しているようです。

catch(:ctrl_c) do
  trap("SIGINT") { throw :ctrl_c }
  (1.. ).each {|n| print "."; sleep 0.5 }
end

# SIGINT trapped -> throw called -> catch block exits
#   ruby covthrow1p.rb
#   => ........

または、throw メソッドに 2 番目のパラメータを指定します。

def stop_script
  puts 'CTRL_C seen'
  exit
end

catch(:ctrl_c) do
  trap("SIGINT") { throw :ctrl_c, stop_script }
  (1.. ).each {|n| print "."; sleep 0.5 }
end

# SIGINT trapped -> throw called -> catch executes stop_script & exits block
#   ruby covthrow2p.rb
#   => ......CTRL_C seen

キーパラメータのみを指定して throw を単独 (裸) で使用すると、失敗します。

def stop_script
  puts 'CTRL_C seen'
  exit
end

trap("SIGINT") { throw :ctrl_c }
(1.. ).each {|n| print "."; sleep 0.5 }

# SIGINT trapped -> throw called -> No catch block -> UncaughtThrowError
#   ruby nkdthrow1p.rb
#   => .......nkdthrow1p.rb:8:in `throw': uncaught throw :ctrl_c (UncaughtThrowError)

ただし、2 番目のパラメーターを指定して裸のスローを使用すると、成功します。

def stop_script
  puts 'CTRL_C seen'
  exit
end

trap("SIGINT") { throw :ctrl_c, stop_script }
(1.. ).each {|n| print "."; sleep 0.5 }


# SIGINT trapped -> throw called -> No catch block -> call stop_script ???
#   ruby nkdthrow2p.rb
#   => ......CTRL_C seen

これは意図された動作ですか?実装の成果物?バグ?一般的には無害に見えますが、混乱を招く動作を引き起こす可能性があります。

この例は、Windows 10 上で実行されている Ruby 3.2.2 でのものです。

解決策

2 番目の例の解釈は間違っています。完全な実行を確認できるように、今のところ exit をコメントアウトしましょう。

def stop_script
  puts 'CTRL_C seen'
  # exit
end

catch(:ctrl_c) do
  trap("SIGINT") { throw :ctrl_c, stop_script }
  (1.. ).each {|n| print "."; sleep 0.5 }
end

実際に何が起こるか:

4 番目の例は同等ですが、最初と最後のステップが欠けているだけです。

重要なのは、スローする 2 番目のパラメーターは、戻り値として catch に渡される値のみであるということです。これには「後でこれを評価する」という魔法の機能はありません。メソッドの引数と同様に、throw が呼び出される前に評価されます。

スクリプトが自然な終了に達するまでにスローが catch によってキャッチされなかった場合、例外が出力されます。ただし、最後の例では、exit が実行され、スクリプトがその場で終了すると、例外状況は発生しません。