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/1rasied a runtime error:iex> raise "oops"
** (RuntimeError) oopsraise/2raise the error name and a list of keyword arguments:iex> raise ArgumentError, message: "invalid argument foo"
** (ArgumentError) invalid argument foodefexceptiondefine 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 messagetry/rescueconstruct: Erroes can be resucediex> try do
...> raise "oops"
...> rescue
...> e in RuntimeError -> e
...> end
%RuntimeError{message: "oops"}
try/rescue constructIn 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