アルパカログ

Webエンジニアでマネージャーな人がプログラミングやマネジメントの話題を中心に書いているブログです。

Elixir エラーハンドリング構文try~catchとtry~rescueの違い

急いでいる人向けまとめ

  • throw/1exitを捕捉したい場合はtry~catch
  • raiseを含むそれ以外のエラーを捕捉したい場合はtry~rescue

f:id:otoyo0122:20200816223704p:plain:w300

エラーハンドリング構文と言えばJavaScriptではtry~catch, Rubyではbegin~rescue, Pythonではtry~except があります。

そう、多くの言語でエラーハンドリング構文は1つです。しかしElixirには2種類のエラーハンドリング構文があります(!?)。

それはタイトルにもある通りtry~catchtry~rescueです。

このエントリでは2種類のエラーハンドリング構文try~catchtry~rescueにどんな違いがあるのか説明します。

Elixirにおけるエラーハンドリングの基本

Elixirでは前提としてexample/1というメソッドは{:ok, result}{:error, reason}を返すように設計します。

case example(foo) do
  {:ok, result} -> # do_something
  {:error, reason} -> # do_something
end

戻り値としてエラーを返すのはGoに似ていますね。

result だけを返すかエラーを発生させるメソッドはメソッド名の末尾に!を付けて定義します。

result = example!(foo)

example!/1がエラーを発生させた場合、エラーをキャッチしなければプログラムは終了します。

ではエラーをキャッチするにはtry~catchtry~rescueのどちらを使えば良いでしょうか?

そのためにはまずElixirにおける2つのエラー発生方法を知る必要があります。

Elixirにおける2つのエラー発生方法とハンドリング

Elixirではエラーを発生させる方法としてthrow/1raiseがあります。

throw/1

throw/1は、ある値を投げ、キャッチするために使います。が、あまり使うことはないようです。

実際には新しいElixirコードでこれらを使うのは非常にまれです

throw/1で投げた値はtry~catchで捕捉することができます。

try do
  throw(1)
catch
  x -> "Caught: #{x}"
end

またtry~catchではexitも捕捉できます。

raise

通常、明示的にエラーを発生させたいときはraise/1raise/2を使います。

raise "Oh no!"
raise ArgumentError, message: "the argument value is invalid"

raiseで発生させたエラーや、その他のランタイムエラーはtry~rescueを使って捕捉することができます。

try do
  do_something!(foo)
rescue
  e in ArgumentError -> IO.puts("the argument value is invalid")
  e -> IO.puts("error: " <> inspect(e))
end

またRubyのensure, Pythonのfinallyに相当するafter句もあります。

おわりに

このエントリではElixirのエラーハンドリング構文try~catchtry~rescueの違いを説明しました。

外部ライブラリを使う際などは、中でthrow/1が使われているのか、raiseが使われているのか気を付けてください。

私はその違いを理解しないまま本番にコードを反映してしまって...というのをやったことがあります。

参考になった方は、ぜひ「はてブ」やSNSでシェアしていただけると嬉しいです。

参考文献

他のElixirのエントリも見る

このブログでは他にもElixirのエントリを書いています。興味のある方は下記からご覧ください。

alpacat.hatenablog.com