Error Handling

Pattern Matching

case File.read("example.txt") do
  {:ok, content} -> 
    IO.puts("File content: #{content}")
  {:error, reason} -> 
    IO.puts("Error reading file: #{reason}")
end

with

with is handy for match result of a serial of operations:

with {:ok, file} <- File.open("example.txt"),
     content = IO.read(file),
     :ok <- File.close(file),
     {:ok, data} <- Jason.decode(content) do
  data
else
  {:error, reason} -> 
    IO.puts("Error: #{reason}")
  _ -> 
    IO.puts("Unknown error occurred")
end

with allows you to focus on the successful path of the computation. It is called "Railway-oriented programming".

This is a common approach in functional programming languages and others where exceptions aren't the primary error handling mechanism.

In haskell there is do and >>=.

In rust to work with Result<T, E> and Option<T> there is ? operator.

In JavaScript, it's Promise chaining using .then().

try/rescue

For catching exceptions(not common in idiomatic Elixir):

try do
  String.to_integer("not_a_number")
rescue
  ArgumentError -> "Invalid argument"
  e in RuntimeError -> "Runtime error: #{e.message}"
  _ -> "Unknown error"
after
  # like finally in other languages
  IO.puts("Operation attempted")
end

Handling process termination

# trap to prevent the parent process from exiting
Process.flag(:trap_exit, true)

spawn_link(fn ->
  exit("I am exiting")
end)

Task

task = Task.async(fn -> 
  File.read!("missing_file.txt")
end)

case Task.yield(task, 5000) || Task.shutdown(task) do
  {:ok, result} -> 
    result
  nil -> 
    "Timeout"
  {:exit, reason} -> 
    IO.puts inspect(reason)
end