README.md

# EarlyReturn

Early return support for Elixir functions.

## Installation

Add `early_return` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:early_return, "~> 1.0.0"}
  ]
end
```

## Usage

Add `use EarlyReturn` to any module to enable the `return` macro:

```elixir
defmodule MyModule do
  use EarlyReturn

  def divide(a, b) do
    if b == 0, do: return {:error, :division_by_zero}

    {:ok, a / b}
  end
end
```

`return` with no arguments returns `nil`:

```elixir
def maybe_process(data) do
  if invalid?(data), do: return()

  do_work(data)
end
```

Both `def` and `defp` are supported. User-defined `catch` and `rescue` blocks are preserved:

```elixir
def fetch(url) do
  if cached = Cache.get(url), do: return cached

  HTTP.get!(url)
rescue
  e in HTTP.Error -> {:error, e.reason}
end
```

## How it works

`use EarlyReturn` replaces `Kernel.def/2` and `Kernel.defp/2` with versions that wrap function bodies in a `catch` block. The `return` macro expands to a `throw`, which is caught by the wrapping `catch` and returned as the function's result.

## Caveats

### Non-local return from anonymous functions

`return` inside a `fn` will return from the enclosing `def`, not from the `fn` itself. This is similar to Ruby's `return` inside blocks:

```elixir
def first_even(list) do
  Enum.each(list, fn x ->
    if rem(x, 2) == 0, do: return x  # returns from first_even, not the fn
  end)

  nil
end
```

### No process boundary crossing

`throw` does not cross process boundaries. Using `return` inside `spawn`, `Task.async`, or similar will crash the child process with `{:nocatch, {:early_return, value}}`.

### Interaction with user-defined `try/catch`

If you write your own `try/catch` inside the function body with a catch-all clause, it will intercept the `return` throw before it reaches the function-level handler:

```elixir
def example do
  try do
    return :early  # caught by the try below, not by def
  catch
    x -> x  # swallows the return
  end
end
```

## Formatter

To allow `return value` without parentheses, add `:early_return` to your formatter's `:import_deps`:

```elixir
# .formatter.exs
[
  import_deps: [:early_return],
  inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
```