Kernel.SpecialForms.try

You're seeing just the macro 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")
end

The 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
end

Erlang errors

Erlang errors are transformed into Elixir ones when rescuing:

try do
  :erlang.error(:badarg)
rescue
  ArgumentError -> :ok
end
#=> :ok

The 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
#=> :ok

In 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)}")
end

Catching 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)}")
end

The 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
end

If 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
  end

However, 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
end

Similarly, 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
end

This 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}
end

Variable 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