Skip to main content

Errors Handling

There are two styles of software engineering to deal with error:

  • Erlang Style: Embrace failures and explicitly design programs to be resilient to partial faults.

  • SQLite Style: Overcome an unreliable environment at the cost of rigorous engineering.

try, catch and rescue

Elixir has three error mechanisms: errors, throws, and exits.

Remarks about when each should be used.

Errors

Errors (or exceptions) are used when exceptional things happen in the code.

  • raise/1 rasied a runtime error:

    iex> raise "oops"
    ** (RuntimeError) oops
  • raise/2 raise the error name and a list of keyword arguments:

    iex> raise ArgumentError, message: "invalid argument foo"
    ** (ArgumentError) invalid argument foo
  • defexception define your own errors:

    iex> defmodule MyError do
    iex> defexception message: "default message"
    iex> end
    iex> raise MyError
    ** (MyError) default message
    iex> raise MyError, message: "custom message"
    ** (MyError) custom message
  • try/rescue construct: Erroes can be resuced

    iex> try do
    ...> raise "oops"
    ...> rescue
    ...> e in RuntimeError -> e
    ...> end
    %RuntimeError{message: "oops"}
Rarely use try/rescue construct

In practice, Elixir developers rarely use the try/rescue construct.

For example, many languages would force you to rescue an error when a file cannot be opened successfully. Elixir instead provides a File.read/1 function which returns a tuple containing information about whether the file was opened successfully:

iex> File.read("hello")
{:error, :enoent}
iex> File.write("hello", "world")
:ok
iex> File.read("hello")
{:ok, "world"}

For the cases where you do expect a file to exist (and the lack of that file is truly an error) you may use File.read!/1:

iex> File.read!("unknown")
** (File.Error) could not read file "unknown": no such file or directory
(elixir) lib/file.ex:272: File.read!/1

The convention is to create a function (foo) which returns {:ok, result} or {:error, reason} tuples and another function (foo!, same name but with a trailing !) that takes the same arguments as foo but which raises an exception if there’s an error. foo! should return the result (not wrapped in a tuple) if everything goes fine. The File module is a good example of this convention.

@spec read(Path.t()) :: {:ok, binary} | {:error, posix}
def read(path) do
:file.read_file(IO.chardata_to_string(path))
end

@doc """
Returns a binary with the contents of the given filename,
or raises a `File.Error` exception if an error occurs.
"""
@spec read!(Path.t()) :: binary
def read!(path) do
case read(path) do
{:ok, binary} ->
binary

{:error, reason} ->
raise File.Error, reason: reason, action: "read file", path: IO.chardata_to_string(path)
end
end

References