Appearance
try, catch, and rescue
Source: Elixir guide, try, catch, and rescue.
Elixir has three error mechanisms: errors, throws and exits.
Errors
Errors (exceptions) are for exceptional things. Raise with raise/1 or raise/2; define your own with defexception:
elixir
raise "oops" #=> ** (RuntimeError) oops
raise ArgumentError, message: "invalid argument foo"
defmodule MyError do
defexception message: "default message"
end
raise MyError #=> ** (MyError) default messageRescue with try/rescue:
elixir
try do
raise "oops"
rescue
e in RuntimeError -> e # or just `RuntimeError -> "Error!"`
end
#=> %RuntimeError{message: "oops"}Rarely rescued: use tuples
Most functions return tagged tuples and let you choose, with case:
elixir
case File.read("hello") do
{:ok, body} -> IO.puts("Success: #{body}")
{:error, reason} -> IO.puts("Error: #{reason}")
endBy convention, foo returns {:ok, result} or {:error, reason}, and foo! returns the bare result or raises. When a missing file really is an error, use File.read!/1.
Fail fast / let it crash
For unexpected failures, don't rescue: let the process die. Processes share nothing, so a crash can't corrupt another process, and a supervisor starts a fresh one. For expected failures, like a user typing a wrong filename, use File.read/1 and report it.
Reraise
Rescue to log, then reraise e, __STACKTRACE__ so the exception keeps its value and origin. Errors are never for flow control. For that, there are throws.
Throws
throw a value and catch it. It's only for when a value can't be retrieved any other way, such as bailing out of Enum.each/2. In practice Enum.find/2 does it:
elixir
Enum.find(-50..50, &(rem(&1, 13) == 0)) #=> -39Exits
When a process dies, it sends an exit signal, which supervisors listen for. exit/1 sends one explicitly, and catch :exit, _ can catch it, though that's even rarer than try/catch.
elixir
def matched_catch do
exit(:timeout)
catch
:exit, :timeout -> {:error, :timeout}
endafter, else, and the order
try/after cleans up whether or not the block raised. It's a soft guarantee: if a linked process exits, after doesn't run. Files, ETS tables and sockets are linked to the process and are closed anyway when it crashes. else matches the result of the do block when nothing was raised, and errors inside else aren't caught.
elixir
try do
1 / 2
rescue
ArithmeticError -> :infinity
else
y when y < 1 and y > -1 -> :small
_ -> :large
end
#=> :smallVariable scope
Like case and if, nothing bound inside try, rescue, catch, else or after leaks out. Return the value of the try instead:
elixir
what_happened =
try do
raise "fail"
:did_not_raise
rescue
_ -> :rescued
end
#=> :rescuedVariables bound in the try body aren't visible in rescue, after or else either: the body may have failed before they were bound.