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