Error Handling
22.1 Philosophy
Use tagged tuples ({:ok, v} / {:error, reason}) for expected failures.
Use exceptions for unexpected situations / programmer errors.
Use "let it crash" + supervisors for process-level failures (§30–33).
Convention: fun returns tuples, fun! raises.
File.read("x") # {:error, :enoent}
File.read!("x") # ** (File.Error) could not read file "x": no such file or directory22.2 Raising and rescuing
Functions can use implicit try:
raise "oops" # RuntimeError
raise ArgumentError, message: "bad arg"
raise ArgumentError, "bad arg"
try do
String.to_integer("abc")
rescue
e in ArgumentError -> IO.puts("Invalid: #{Exception.message(e)}")
e -> reraise e, __STACKTRACE__
else
value -> value * 2 # runs if no exception
after
IO.puts("always runs") # cleanup
enddef safe_div(a, b) do
a / b
rescue
ArithmeticError -> :infinity
end22.3 Custom exceptions
defmodule InsufficientFunds do
defexception [:needed, message: "insufficient funds"]
@impl true
def message(%{needed: n}), do: "insufficient funds: need #{n} more"
end
raise InsufficientFunds, needed: 5022.4 throw / catch (non-local return; rare)
try do
Enum.each(1..100, fn x -> if x == 42, do: throw({:found, x}) end)
catch
{:found, x} -> x
end22.5 exit
try do
exit(:shutdown)
catch
:exit, reason -> {:exited, reason}
end
# Catch anything: kind is :error | :throw | :exit
try do
risky()
catch
kind, value -> {kind, value}
end22.6 Common built-in exceptions
ArgumentError, ArithmeticError, KeyError, MatchError, FunctionClauseError, CaseClauseError, CondClauseError, WithClauseError, UndefinedFunctionError, Protocol.UndefinedError, RuntimeError, File.Error, Enum.EmptyError, BadMapError, BadStructError, SystemLimitError.
22.7 Error-handling patterns
# with for chains
with {:ok, a} <- step1(), {:ok, b} <- step2(a), do: {:ok, b}
# Normalizing errors
def fetch(id) do
case Repo.get(User, id) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end