try .. rescue
can be used to capture and evaluate errors that are raised inside a block.
For example:
try do
raise RuntimeError, "error"
rescue
e in RuntimeError -> :error
end
try
.raise/1
.rescue
section, we pattern match on the Module name of the error raised
->
:
e
is matched to the error struct.in
is a keyword.RuntimeError
is the module to matched, but can match on any error module, or _
all errors.Errors (sometimes also called "exceptions") that you rescue this way are structs. Different error structs have different keys. Under the "exceptions" section in the standard library you can find a list of all predefined errors.
# ArithmeticError caused by division by zero
%ArithmeticError{message: "bad argument in arithmetic expression"}
# Protocol.UndefinedError caused by passing `nil` to `Enum.count/1`
%Protocol.UndefinedError{description: "", protocol: Enumerable, value: nil}
Rescuing errors in Elixir is done very rarely. Usually the rescued error is logged or sent to an external monitoring service, and then reraised. This means we usually don't care about the internal structure of the specific error struct.
The Exceptions concept describes how to define custom error structs.
Avoid programming patterns that use errors to control logical flow. This is an anti-pattern in Elixir.
# Avoid using errors for control-flow.
try do
{:ok, value} = MyModule.janky_function()
"All good! #{value}."
rescue
e in RuntimeError ->
reason = e.message
"Uh oh! #{reason}."
end
# Rather, use control-flow structures for control-flow.
case MyModule.janky_function() do
{:ok, value} -> "All good! #{value}."
{:error, reason} -> "Uh oh! #{reason}."
end
As it's written in Elixir's getting started guide:
It’s up to your application to decide if an error while [performing an action] is exceptional or not. That’s why Elixir doesn’t impose exceptions on [...] functions. Instead, it leaves it up to the developer to choose the best way to proceed.