初心者がつまずくRubyのraise
とthrow
についてちょうどいい記事がなかったのでまとめました。
##raise
raise
について軽くおさらいします。C++、Javaなどのtry/throw/catch
に近い
raise
できるのはException
クラスを継承したクラス/インスタンスのみ
raise Fixnum #=> TypeError: exception class/object expected
raise 5 #=> TypeError: exception class/ object expected
ExceptionにはSignalException
やScriptError
なども含まれるので、何も考えずrescue Exception
などと書いてしまうと、スクリプトを強制終了したいときや構文エラーのときもrescueされてしまう。大抵はStandardError
以下を使う。
引数なしrescue
はrescue StandardError
と同値
def default_rescue
raise StandardError
rescue # StandardError
puts '救われたよかった'
end
default_rescue #=> "救われたよかった"
また、rescue
されるオブジェクトには、サブクラスとインスタンスも含まれる。
# 全部無印rescueでも処理されるよ
raise StandardError # StandardErrorクラスそのもの
raise StandardError.new # StandardErrorのインスタンス
raise ArgumentError # StandardErrorのサブクラス
4 / 0 # ZeroDivisionErrorもStandardErrorのサブクラス
同じ階層に複数置けるよ
begin
raise NameError
rescue NameError
puts 'name'
rescue StandardError
puts 'standard'
end # この場合"name"が表示される
else
、ensure
、fail
、単体raise
や、返り値についてにも書きたいが、割愛
重要なのは「エラーメッセージやスタックトレースを表示するためのもの」だということ
begin
raise ArgumentError.new('引数が違う')
rescue ArgumentError => e
puts e.message
puts e.backtrace.inspect
end
##throw
あまり直感的ではないけど、Rubyでのthrow
の用途はCなどでいうgoto
に近い。制御フローに使う。
なんでも投げれる1
catch Object do
throw Object
end
ただし、throw
するオブジェクトはcatch
するオブジェクトと全く同じものでなくてはならない。==
や===
で比較もかけてくれない。
catch "sample" do
throw "sample"
end
# 内容は一緒でも違うオブジェクトなのでcatchされない
# "sample".object_id != "sample".object_id
因みに、catch
に失敗したときUncaughtThrowError
が吐かれる2が、これはrescue
可能。両者の違いをよく理解してない人が聞くと「ん?」となる瞬間(らしい)。
また、catch
したオブジェクトを参照することもできない。代わりに二つ目の引数で返り値を指定する。
これらの理由から、結局普通はSymbol
を使う。
def find_something
# stub
throw :found, "found!"
end
puts "catch前"
msg = catch(:found) do
puts "throw前"
find_something
puts "throw後" # 実行されないよ
end
puts msg
puts "catch後"
# catch前
# throw前
# found!
# catch後
あとは、rescue
と違って複数catch
したい場合はネストする必要があります。
##結局何が違うの
ここまで読めば大体わかると思いますが、両者はまったく別物です。
####用途が違う
raise
..rescue
はエラーハンドリングのためでした。throw
..catch
に例外/エラーは関係ありません。スタックから素早く抜け出したいときに使えます。
####パフォーマンスが違う
raise
はバックトレースをメモりながら戻ってくるのに対して、throw
はジャンプするだけなので、実行時間が全然変わってきます。
####愛され方が違う
raise
..rescue
はよく使われるの対してthrow
..catch
は誰も使ってくれません。当たり前なんですが、それにしてもgoto
が悪とされてるからって敬遠してる人が多いので、以下に例を挙げます
##そもそもthrow
って何に使うの
ここまで読むとthrow
は忘れてraise
だけを知っておけばいいみたいになりそうなので..
throw
の一番よく見るユースケースは深いループから素早く抜け出すときです。
catch :out do
while true
while true
while true
while true
throw :out, "脱出"
end
end
end
end
end #=> "脱出"
そもそもこういうコードを書いている時点で設計がおかしいという議論がありますが、現実では複数break
が欲しくなる状況がごく稀にでてきますし、知っててもいいんじゃないかと
関数にしちゃってreturn
で抜け出しても同じことができるんじゃないかという議論もあります。そうですね。でもスコープが変わっちゃうしたまにはthrow
も使ってね
goto
のようなことをしているのにインデントのおかげでそこまで読みにくくないというのがthrow
の良いところだと思います。
また、有名どころではSinatra
がhalt
をcatch/throw
を使って実装しています。3 halt
使ってる人はthrow
に優しくしてあげて
# Exit the current block, halts any further processing
# of the request, and returns the specified response.
def halt(*response)
response = response.first if response.length == 1
throw :halt, response
end
##結論
エラーハンドリングにはraise
..rescue
を使いましょう。
throw
..catch
はgoto
のような制御構文ですが、goto
のワルなところをできるだけ削ったマルいやつになってます。いつか使うときがくるかも分からないので一応覚えておくといいかも。