# Errors: Tagged Tuples, Exceptions and Bang Functions — Elixir & Phoenix

Source: https://www.geekswithgeeks.com/en/elixir-phoenix/d-errors

> Handle expected and unexpected failures idiomatically.

## Expected errors versus exceptional ones

Elixir distinguishes **expected failures**, which are part of normal flow, from **unexpected** ones. Expected failures are returned as **tagged tuples**, `{:ok, value}` or `{:error, reason}`, and handled with `case` or `with`. Many library functions come in two forms: `File.read/1` returns a tuple, while **`File.read!/1`** (a **bang function**) returns the value directly or **raises** an exception. Use the bang version when failure means a bug or a broken environment and you have no useful way to recover. Exceptions are raised with `raise` and handled with `try/rescue`, and custom exceptions are defined with `defexception`. However, in Elixir **`try/rescue` is used sparingly**: the OTP philosophy is **"let it crash"**. Instead of defensive code for every possible error, write the happy path, let a process crash on unexpected failures and rely on a **supervisor** to restart it in a known good state. Also available are `throw/catch` (rare, for non-local returns) and **exits**, which are signals between processes.

## Tuples, bang functions and custom exceptions

Return tuples for expected errors, raise for bugs.

```elixir
defmodule Shop.InvalidCoupon do
  defexception [:code, message: "invalid coupon"]

  @impl true
  def message(%{code: code}), do: "invalid coupon: #{code}"
end

defmodule Shop.Coupons do
  @coupons %{"FESTIVE10" => 10, "WELCOME5" => 5}

  def fetch(code) do
    case Map.fetch(@coupons, String.upcase(code)) do
      {:ok, percent} -> {:ok, percent}
      :error -> {:error, :unknown_coupon}
    end
  end

  def fetch!(code) do
    case fetch(code) do
      {:ok, percent} -> percent
      {:error, _} -> raise Shop.InvalidCoupon, code: code
    end
  end
end

case Shop.Coupons.fetch("festive10") do
  {:ok, percent} -> IO.puts("#{percent}% off")
  {:error, :unknown_coupon} -> IO.puts("Coupon not recognised")
end

case File.read("config/prices.json") do
  {:ok, body} -> IO.puts("read #{byte_size(body)} bytes")
  {:error, reason} -> IO.puts("cannot read prices: #{:file.format_error(reason)}")
end

try do
  Shop.Coupons.fetch!("BOGUS")
rescue
  e in Shop.InvalidCoupon -> IO.puts(Exception.message(e))
end
```

## Let it crash, but not everywhere

Let it crash applies to processes under supervision handling unexpected states. User input errors, such as an invalid coupon, are expected and should return `{:error, reason}` with a helpful message.

**Quiz:** What does a bang function such as File.read!/1 do on failure?

- [ ] Returns {:error, reason}
- [ ] Returns nil
- [x] Raises an exception
- [ ] Retries forever

*Answer:* Raises an exception. Bang functions return the plain value or raise.
