# `Runic.Closure`
[🔗](https://github.com/zblanco/runic/blob/main/lib/closure.ex#L1)

Serializable closure representation for Runic components.

A Closure combines the source AST, runtime bindings, and compile-time
metadata into a single serializable structure. This allows components
to be reconstructed from logs using `term_to_binary/1` and `binary_to_term/1`.

## Fields

- `:source` - The original quoted AST for the closure
- `:bindings` - Map of variable names to their captured values
- `:metadata` - `%Runic.ClosureMetadata{}` for environment reconstruction
- `:hash` - Content-addressable hash of the closure

## Examples

    outer_var = 42
    
    closure = Runic.Closure.new(
      quote(do: fn x -> x + outer_var end),
      %{outer_var: 42},
      __ENV__
    )
    
    # Closures are serializable
    binary = :erlang.term_to_binary(closure)
    roundtrip = :erlang.binary_to_term(binary)
    
    # Can be evaluated in a different context
    {fun, _} = Runic.Closure.eval(closure)
    fun.(10) # => 52

# `t`

```elixir
@type t() :: %Runic.Closure{
  bindings: map(),
  hash: integer() | nil,
  metadata: Runic.ClosureMetadata.t() | nil,
  source: Macro.t()
}
```

# `eval`

Evaluates a closure, returning the result and updated bindings.

This reconstructs the evaluation environment from the closure's metadata
and evaluates the source AST with the stored bindings.

## Options

- `:base_env` - Override the base environment for evaluation

# `new`

Creates a new Closure from source AST, bindings, and caller environment.

Validates that all bindings are serializable before creating the closure.
Raises `ArgumentError` if any binding contains non-serializable values.

# `validate_bindings!`

Validates that all bindings in a map are serializable.

Raises `ArgumentError` if any binding is not serializable.

# `validate_value`

Validates that a value can be serialized with `term_to_binary/1`.

Returns `:ok` if serializable, `{:error, reason}` otherwise.

Note: PIDs, references, and ports can technically be serialized but they
are not valid across sessions/nodes, so we reject them.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
