Kernel.SpecialForms.try
try, go back to Kernel.SpecialForms module for more information.
Evaluates the given expressions and handles any error, exit, or throw that may have happened.
Examples
try do
do_something_that_may_fail(some_arg)
rescue
ArgumentError ->
IO.puts("Invalid argument given")
catch
value ->
IO.puts("Caught #{inspect(value)}")
else
value ->
IO.puts("Success! The result was #{inspect(value)}")
after
IO.puts("This is printed regardless if it failed or succeeded")
endThe rescue clause is used to handle exceptions while the catch
clause can be used to catch thrown values and exits.
The else clause can be used to control flow based on the result of
the expression. catch, rescue, and else clauses work based on
pattern matching (similar to the case special form).
Calls inside try/1 are not tail recursive since the VM needs to keep
the stacktrace in case an exception happens. To retrieve the stacktrace,
access __STACKTRACE__/0 inside the rescue or catch clause.
rescue clauses
Besides relying on pattern matching, rescue clauses provide some
conveniences around exceptions that allow one to rescue an
exception by its name. All the following formats are valid patterns
in rescue clauses:
# Rescue a single exception without binding the exception
# to a variable
try do
UndefinedModule.undefined_function
rescue
UndefinedFunctionError -> nil
end
# Rescue any of the given exception without binding
try do
UndefinedModule.undefined_function
rescue
[UndefinedFunctionError, ArgumentError] -> nil
end
# Rescue and bind the exception to the variable "x"
try do
UndefinedModule.undefined_function
rescue
x in [UndefinedFunctionError] -> nil
end
# Rescue all kinds of exceptions and bind the rescued exception
# to the variable "x"
try do
UndefinedModule.undefined_function
rescue
x -> nil
endErlang errors
Erlang errors are transformed into Elixir ones when rescuing:
try do
:erlang.error(:badarg)
rescue
ArgumentError -> :ok
end
#=> :okThe most common Erlang errors will be transformed into their
Elixir counterpart. Those which are not will be transformed
into the more generic ErlangError:
try do
:erlang.error(:unknown)
rescue
ErlangError -> :ok
end
#=> :okIn fact, ErlangError can be used to rescue any error that is
not a proper Elixir error. For example, it can be used to rescue
the earlier :badarg error too, prior to transformation:
try do
:erlang.error(:badarg)
rescue
ErlangError -> :ok
end
#=> :ok
catch clauses
The catch clause can be used to catch thrown values, exits, and errors.
Catching thrown values
catch can be used to catch values thrown by Kernel.throw/1:
try do
throw(:some_value)
catch
thrown_value ->
IO.puts("A value was thrown: #{inspect(thrown_value)}")
endCatching values of any kind
The catch clause also supports catching exits and errors. To do that, it
allows matching on both the kind of the caught value as well as the value
itself:
try do
exit(:shutdown)
catch
:exit, value ->
IO.puts("Exited with value #{inspect(value)}")
end
try do
exit(:shutdown)
catch
kind, value when kind in [:exit, :throw] ->
IO.puts("Caught exit or throw with value #{inspect(value)}")
endThe catch clause also supports :error alongside :exit and :throw as
in Erlang, although this is commonly avoided in favor of raise/rescue control
mechanisms. One reason for this is that when catching :error, the error is
not automatically transformed into an Elixir error:
try do
:erlang.error(:badarg)
catch
:error, :badarg -> :ok
end
#=> :ok
after clauses
An after clause allows you to define cleanup logic that will be invoked both
when the block of code passed to try/1 succeeds and also when an error is raised. Note
that the process will exit as usual when receiving an exit signal that causes
it to exit abruptly and so the after clause is not guaranteed to be executed.
Luckily, most resources in Elixir (such as open files, ETS tables, ports, sockets,
and so on) are linked to or monitor the owning process and will automatically clean
themselves up if that process exits.
File.write!("tmp/story.txt", "Hello, World")
try do
do_something_with("tmp/story.txt")
after
File.rm("tmp/story.txt")
end
else clauses
else clauses allow the result of the body passed to try/1 to be pattern
matched on:
x = 2
try do
1 / x
rescue
ArithmeticError ->
:infinity
else
y when y < 1 and y > -1 ->
:small
_ ->
:large
endIf an else clause is not present and no exceptions are raised,
the result of the expression will be returned:
x = 1
^x =
try do
1 / x
rescue
ArithmeticError ->
:infinity
endHowever, when an else clause is present but the result of the expression
does not match any of the patterns then an exception will be raised. This
exception will not be caught by a catch or rescue in the same try:
x = 1
try do
try do
1 / x
rescue
# The TryClauseError cannot be rescued here:
TryClauseError ->
:error_a
else
0 ->
:small
end
rescue
# The TryClauseError is rescued here:
TryClauseError ->
:error_b
endSimilarly, an exception inside an else clause is not caught or rescued
inside the same try:
try do
try do
nil
catch
# The exit(1) call below can not be caught here:
:exit, _ ->
:exit_a
else
_ ->
exit(1)
end
catch
# The exit is caught here:
:exit, _ ->
:exit_b
endThis means the VM no longer needs to keep the stacktrace once inside
an else clause and so tail recursion is possible when using a try
with a tail call as the final call inside an else clause. The same
is true for rescue and catch clauses.
Only the result of the tried expression falls down to the else clause.
If the try ends up in the rescue or catch clauses, their result
will not fall down to else:
try do
throw(:catch_this)
catch
:throw, :catch_this ->
:it_was_caught
else
# :it_was_caught will not fall down to this "else" clause.
other ->
{:else, other}
endVariable handling
Since an expression inside try may not have been evaluated
due to an exception, any variable created inside try cannot
be accessed externally. For instance:
try do
x = 1
do_something_that_may_fail(same_arg)
:ok
catch
_, _ -> :failed
end
x
#=> unbound variable "x"In the example above, x cannot be accessed since it was defined
inside the try clause. A common practice to address this issue
is to return the variables defined inside try:
x =
try do
x = 1
do_something_that_may_fail(same_arg)
x
catch
_, _ -> :failed
end