LoginSignup
63
47

More than 5 years have passed since last update.

raise~rescue,とthrow~catchの違い、本当にわかってる?まとめた

Last updated at Posted at 2016-02-17

初心者がつまずくRubyのraisethrowについてちょうどいい記事がなかったのでまとめました。

raise

raiseについて軽くおさらいします。C++、Javaなどのtry/throw/catchに近い

raiseできるのはExceptionクラスを継承したクラス/インスタンスのみ

raise Fixnum #=> TypeError: exception class/object expected
raise 5 #=> TypeError: exception class/ object expected

ExceptionにはSignalExceptionScriptErrorなども含まれるので、何も考えずrescue Exceptionなどと書いてしまうと、スクリプトを強制終了したいときや構文エラーのときもrescueされてしまう。大抵はStandardError以下を使う。

引数なしrescuerescue 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"が表示される

elseensurefail、単体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の良いところだと思います。

また、有名どころではSinatrahaltcatch/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..catchgotoのような制御構文ですが、gotoのワルなところをできるだけ削ったマルいやつになってます。いつか使うときがくるかも分からないので一応覚えておくといいかも。


  1. 1.9.0以前ではSymbol以外はダメだったようです 

  2. 2.2.0以前ではArgumentError 

  3. base.rb 

63
47
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
63
47