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

初心者がつまずくRubyの`raise`と`throw`についてちょうどいい記事がなかったのでまとめました。
##`raise`
`raise`について軽くおさらいします。C++、Javaなどの`try/throw/catch`に近い

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

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

[Exception](http://ruby-doc.org/core-2.3.0/Exception.html)には`SignalException`や`ScriptError`なども含まれるので、何も考えず`rescue Exception`などと書いてしまうと、スクリプトを強制終了したいときや構文エラーのときもrescueされてしまう。大抵は`StandardError`以下を使う。

引数なし`rescue`は`rescue StandardError`と同値

```rb
def default_rescue
  raise StandardError
rescue # StandardError
  puts '救われたよかった'
end

default_rescue #=> "救われたよかった" 
```

また、`rescue`されるオブジェクトには、サブクラスとインスタンスも含まれる。

```rb
# 全部無印rescueでも処理されるよ
  raise StandardError       # StandardErrorクラスそのもの
  raise StandardError.new   # StandardErrorのインスタンス
  raise ArgumentError       # StandardErrorのサブクラス
  4 / 0                     # ZeroDivisionErrorもStandardErrorのサブクラス
```
同じ階層に複数置けるよ

```rb
begin  
  raise NameError
rescue NameError
  puts 'name'
rescue StandardError
  puts 'standard'  
end  # この場合"name"が表示される
```

`else`、`ensure`、`fail`、単体`raise`や、返り値についてにも書きたいが、割愛
重要なのは「エラーメッセージやスタックトレースを表示するためのもの」だということ

```rb
begin  
  raise ArgumentError.new('引数が違う')  
rescue ArgumentError => e  
  puts e.message  
  puts e.backtrace.inspect  
end 
```

##`throw`

あまり直感的ではないけど、Rubyでの`throw`の用途はCなどでいう`goto`に近い。制御フローに使う。

なんでも投げれる[^1]

```rb
catch Object do
  throw Object
end
```

ただし、`throw`するオブジェクトは`catch`するオブジェクトと全く同じものでなくてはならない。`==`や`===`で比較もかけてくれない。

```rb
catch "sample" do
  throw "sample"
end 
# 内容は一緒でも違うオブジェクトなのでcatchされない
# "sample".object_id != "sample".object_id
```

因みに、`catch`に失敗したとき`UncaughtThrowError`が吐かれる[^2]が、これは`rescue`可能。両者の違いをよく理解してない人が聞くと「ん？」となる瞬間（らしい）。

また、`catch`したオブジェクトを参照することもできない。代わりに二つ目の引数で返り値を指定する。
これらの理由から、結局普通は`Symbol`を使う。

```rb
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`に例外/エラーは関係ありません。スタックから素早く抜け出したいときに使えます。
####[パフォーマンス](https://gist.github.com/harvath/9fa3f0cf11dda41d8302)が違う
`raise`はバックトレースをメモりながら戻ってくるのに対して、`throw`はジャンプするだけなので、実行時間が全然変わってきます。
####愛され方が違う
`raise`..`rescue`はよく使われるの対して`throw`..`catch`は誰も使ってくれません。当たり前なんですが、それにしても`goto`が悪とされてるからって敬遠してる人が多いので、以下に例を挙げます
##そもそも`throw`って何に使うの
ここまで読むと`throw`は忘れて`raise`だけを知っておけばいいみたいになりそうなので..
`throw`の一番よく見るユースケースは深いループから素早く抜け出すときです。

```rb
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`に優しくしてあげて

```rb 
# 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`のワルなところをできるだけ削ったマルいやつになってます。いつか使うときがくるかも分からないので一応覚えておくといいかも。

[^1]: `1.9.0`以前では`Symbol`以外はダメだったようです
[^2]: `2.2.0`以前では`ArgumentError`
[^3]: [base.rb](https://github.com/sinatra/sinatra/blob/1666dade4fa583462bfd6dc45770389c60c32eac/lib/sinatra/base.rb)