Do you want to pick up from where you left of?
Take me there

Error Handling

Although more common to return the {:error, reason} tuple, Elixir supports exceptions and in this lesson we’ll look at how to handle errors and the different mechanisms available to us.

In general the convention in Elixir is to create a function (example/1) which returns {:ok, result} and {:error, reason} and a separate function (example!/1) that returns the unwrapped result or raises an error.

This lesson will focus on interacting with the latter

General conventions

At the moment, the Elixir community has come to some conventions regarding returning errors:

We generally handle standard flow errors by Pattern Matching, but in this lesson, we are focusing on the second case - on the exceptions.

Often, in public APIs, you can also find a second version of the function with a ! (example!/1) that returns the unwrapped result or raises an error.

Error Handling

Before we can handle errors we need to create them and the simplest way to do so is with raise/1:

iex> raise "Oh no!"
** (RuntimeError) Oh no!

If we want to specify the type and message, we need to use raise/2:

iex> raise ArgumentError, message: "the argument value is invalid"
** (ArgumentError) the argument value is invalid

When we know an error may occur, we can handle it using try/rescue and pattern matching:

iex> try do
...>   raise "Oh no!"
...> rescue
...>   e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> end
An error occurred: Oh no!
:ok

It’s possible to match multiple errors in a single rescue:

try do
  opts
  |> Keyword.fetch!(:source_file)
  |> File.read!()
rescue
  e in KeyError -> IO.puts("missing :source_file option")
  e in File.Error -> IO.puts("unable to read source file")
end

After

At times it may be necessary to perform some action after our try/rescue regardless of error. For this we have try/after. If you’re familiar with Ruby this is akin to begin/rescue/ensure or in Java try/catch/finally:

iex> try do
...>   raise "Oh no!"
...> rescue
...>   e in RuntimeError -> IO.puts("An error occurred: " <> e.message)
...> after
...>   IO.puts "The end!"
...> end
An error occurred: Oh no!
The end!
:ok

This is most commonly used with files or connections that should be closed:

{:ok, file} = File.open("example.json")

try do
  # Do hazardous work
after
  File.close(file)
end

New Errors

While Elixir includes a number of builtin error types like RuntimeError, we maintain the ability to create our own if we need something specific. Creating a new error is easy with the defexception/1 macro which conveniently accepts the :message option to set a default error message:

defmodule ExampleError do
  defexception message: "an example error has occurred"
end

Let’s take our new error for a spin:

iex> try do
...>   raise ExampleError
...> rescue
...>   e in ExampleError -> e
...> end
%ExampleError{message: "an example error has occurred"}

Throws

Another mechanism for working with errors in Elixir is throw and catch. In practice these occur very infrequently in newer Elixir code but it’s important to know and understand them nonetheless.

The throw/1 function gives us the ability to exit execution with a specific value we can catch and use:

iex> try do
...>   for x <- 0..10 do
...>     if x == 5, do: throw(x)
...>     IO.puts(x)
...>   end
...> catch
...>   x -> "Caught: #{x}"
...> end
0
1
2
3
4
"Caught: 5"

As mentioned, throw/catch are quite uncommon and typically exist as stopgaps when libraries fail to provide adequate APIs.

Exiting

The final error mechanism Elixir provides us with is exit. Exit signals occur whenever a process dies and are an important part of the fault tolerance of Elixir.

To explicitly exit we can use exit/1:

iex> spawn_link fn -> exit("oh no") end
** (EXIT from #PID<0.101.0>) evaluator process exited with reason: "oh no"

While it is possible to catch an exit with try/catch doing so is extremely rare. In almost all cases it is advantageous to let the supervisor handle the process exit:

iex> try do
...>   exit "oh no!"
...> catch
...>   :exit, _ -> "exit blocked"
...> end
"exit blocked"
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!