cf. 17 トライ,キャッチとレスキュー – try, catch and rescue – Elixir
エラー
エラーの例は、例えばアトムに数を足そうとするとみることができる。
iex(1)> :atom + 1
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(:atom, 1)
意図的にランタイムエラーを起こそうというときには、raise/1
マクロを使う。
iex(1)> raise "oops"
** (RuntimeError) oops
その他のエラーは raise/2
マクロにエラーの名前とキーワード引数を渡す。
iex(1)> raise ArgumentError, message: "Invalid argument foo"
** (ArgumentError) Invalid argument foo
モジュールの中で defexception/1
マクロを使うと、自分でエラーを定義することもできる。
iex(1)> defmodule MyError do
...(1)> defexception message: "default message"
...(1)> end
{:module, MyError,
<<70, 79, 82, 49, 0, 0, 14, 252, 66, 69, 65, 77, 69, 120, 68, 99, 0, 0, 1, 40,
131, 104, 2, 100, 0, 14, 101, 108, 105, 120, 105, 114, 95, 100, 111, 99, 115,
95, 118, 49, 108, 0, 0, 0, 4, 104, 2, ...>>, :ok}
iex(2)> raise MyError
** (MyError) default message
iex(2)> raise MyError, message: "custom message"
** (MyError) custom message
try / rescue
エラー(例外)は、try
/ rescue
構造で拾うことができる。
iex(2)> try do
...(2)> raise "oops"
...(2)> rescue
...(2)> e in RuntimeError -> e
...(2)> end
%RuntimeError{message: "oops"}
上の例ではランタイムエラーを拾って、iex で表示するようにエラー自身を返している。
とはいえ、実際には try
/ rescue
構造を使うことはめったにないそうだ。というのも、例えば File.read/1
関数はファイルを開くとき、失敗したらエラーを起こすのではなく、「成功した」/「失敗した」という情報を含んだタプルを返すからだ。
iex(3)> File.read "hello"
{:error, :enoent}
iex(4)> File.write "hello", "world"
:ok
iex(5)> File.read "hello"
{:ok, "world"}
ファイルを開くときに起こりうる複数の結果(成功/失敗)に対応するには、単に case
でパターンマッチングすればいい。
throw / catch
Elixir では後で値を捕れるように trhow / catch 構造が用意されている。たとえば、Enum モジュールで 13 の倍数となるような最初の値を見つけたいとしよう。
iex(6)> try do
...(6)> Enum.each -50..50, fn(x) ->
...(6)> if rem(x, 13) == 0, do: throw(x)
...(6)> end
...(6)> "Got nothing"
...(6)> catch
...(6)> x -> "Got #{x}"
...(6)> end
"Got -39"
exit
Elixir ではすべてのコードは複数のプロセスの中で動いていてたがいにメッセージをやり取りしている。プロセスが死んだとき、exit
という信号を送る。あるいは exit
信号を送ることで明示的に死ぬこともできる。
iex(7)> spawn_link fn -> exit(1) end
** (EXIT from #PID<0.80.0>) 1
Interactive Elixir (1.3.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
exit
は try
/ catch
構造でキャッチできる。
iex(1)> try do
...(1)> exit "I am exiting"
...(1)> catch
...(1)> :exit, _ -> "not really"
...(1)> end
"not really"
try
/ catch
と使うことも珍しいけど、exit
をキャッチするのはもっと珍しいと書いてある。
というのも、プロセスは通常、単にプロセスの exit
信号を待つだけの監視プロセスを備えた監視ツリーにくみこまれて動作していて、監視プロセスが exit
信号を受けとると、監視対象のプロセスは再起動させられるからだ。この監視システムが、エラーの後に「早く失敗する」ことでアプリケーションを既知の初期状態へ戻すことを保証している。らしい。
after
特定の動作の後、リソースをきれいに片付けられることを保証しなければならない場合、try
/ after
が利用できる例えばファイルを開くとき、必ず閉じることを try
/ after
で保証できる。
iex(2)> {:ok, file} = File.open "sample", [:utf8, :write]
{:ok, #PID<0.120.0>}
iex(3)> try do
...(3)> IO.write file, "ola"
...(3)> raise "oops, something went wrong"
...(3)> after
...(3)> File.close(file)
...(3)> end
** (RuntimeError) oops, something went wrong