> For the complete Mojo documentation index, see [llms.txt](/llms.txt).
> Markdown versions of all pages are available by appending .md to any URL (e.g. /docs/manual/basics.md).

# Mojo closure declarations reference

<!-- VERIFIED: Signatures.cpp (parseCaptureList, ParsedCaptureList),
     Signatures.h (CaptureConvention, ParsedCaptureList),
     DeclResolution.cpp (isNonlegacyClosure classification,
     createCaptureValues, setDefaultCaptureForScope),
     unified_closures.mojo, unified_closures_mem.mojo,
     unified_closures_params.mojo,
     unified_closure_captured_param.mojo,
     unified_closure_structs.mojo, unified_closure_inline.mojo.
     Consulted: StructEmitter.cpp, ASTDecl.h, MojoDiags.h -->

A *closure* is a nested function with a *capture list* that
controls how it accesses values from its enclosing scope:

```mojo
def main():
    var multiplier = 3

    def scale(x: Int) {read multiplier} -> Int:
        return x * multiplier

    print(scale(5))  # 15
```

`{read multiplier}` references `multiplier` from the enclosing
scope as an immutable reference. Without the capture list,
referencing any outer value is a compile error.

A closure is created when its enclosing `def` runs and exists
only for the lifetime of that enclosing scope. Mojo doesn't
support escaping closures or async execution.

:::note
A closure is `Copyable` when every value it captures is `Copyable`. In theory,
you could allocate heap memory and copy a closure into it manually. Mojo
doesn't provide a built-in mechanism for heap-allocated or existential
closures.
:::

## Closure syntax

<!-- VERIFIED: From parseCaptureList in Signatures.cpp:1274-1364 -->

```text
def name(argument-list) {capture-list} -> ReturnType:
    body

def name[parameter-list](https://mojolang.org/docs/reference/argument-list.md) {capture-list}
    -> ReturnType:
    body

def name(argument-list) raises {capture-list} -> ReturnType:
    body
```

Effects (`raises`) go between the argument
list and the capture list. The capture list appears immediately
before the return arrow. It can be empty (`{}`) or omitted
entirely; both forms prohibit references to outer values.

The argument list, parameter list, effects, return type, and
`where` clauses follow the same rules as top-level functions.
See [Function declarations](https://mojolang.org/docs/reference/function-declarations.md).

## Capture list grammar

<!-- VERIFIED: parseCaptureList, Signatures.cpp:1274-1364 -->

A capture list is a brace-enclosed, comma-separated sequence of
*entries*:

<!-- markdownlint-disable MD013 -->

| Form           | Meaning                                       |
|----------------|-----------------------------------------------|
| `<conv> name`  | Capture `name` with convention `<conv>`       |
| `<conv>`       | Default convention for all free variables     |
| `name`         | Capture `name` with convention `read`         |
| `<conv> name^` | Move-capture (only with `var` or no `<conv>`) |

<!-- markdownlint-enable MD013 -->

`<conv>` is one of `read`, `mut`, `var`, `ref`. Position within
the list isn't significant: `{mut, var z}` and `{var z, mut}`
are equivalent. Trailing commas are accepted.

At most one entry can omit a name (the default-convention
entry). A second produces:

```text
error: default capture convention was already specified;
       remove the duplicate
```

The `^` marker is only legal on `var` entries or entries with no
convention keyword:

```text
error: '^' requires 'var' convention; write 'var x^' to
       move a capture
```

## Capture conventions

<!-- markdownlint-disable MD013 -->

| Convention | Form                     | Storage in closure                | Lifetime tie to outer |
|------------|--------------------------|-----------------------------------|-----------------------|
| `read`     | `{read name}` / `{read}` | Immutable reference               | Live                  |
| `mut`      | `{mut name}` / `{mut}`   | Mutable reference                 | Live                  |
| `ref`      | `{ref name}` / `{ref}`   | Reference, mutability from origin | Live                  |
| `var`      | `{var name}` / `{var}`   | Owned copy                        | Independent           |
| Move       | `{var name^}`            | Owned, consumed from outer        | Consumes outer        |
| Copyable   | `{var^}`                 | Owned, closure is `Copyable`      | Independent           |

<!-- markdownlint-enable MD013 -->

## `read`

<!-- VERIFIED: unified_closures_mem.mojo:72-80,
     unified_closure_captured_param.mojo:98 -->

Immutable reference. The closure observes the outer value's
current state at each call:

```mojo
def main():
    var limit = 10

    def check(x: Int) {read limit} -> Bool:
        return x < limit

    print(check(5))   # True
    limit = 3
    print(check(5))   # False
```

`{read}` (no name) applies `read` to every free variable in
the body. A bare name without a convention keyword also defaults
to `read`: `{x}` is equivalent to `{read x}`.

## `mut`

<!-- VERIFIED: unified_closures.mojo:384,
     unified_closures_mem.mojo:61 -->

Mutable reference. Writes inside the closure modify the outer
binding:

```mojo
def main():
    var total = 0

    def accumulate(x: Int) {mut total}:
        total += x

    accumulate(10)
    accumulate(20)
    print(total)  # 30
```

`{mut}` (no name) applies mutable-reference capture to every
free variable in the body.

<!--
Removed for now:

The outer binding must be mutable. Capturing an immutable
register-passable name (like a `read` parameter of type `Int`)
with `mut` or `ref` produces the same diagnostic, with the
offending convention substituted into the message:

```text
error: register passible value 'x' can not be captured by
       'mut'. Do you mean 'read'?

error: register passible value 'x' can not be captured by
       'ref'. Do you mean 'read'?
```
-->

## `var`

<!-- VERIFIED: unified_closures_mem.mojo:50-57 -->

Owned copy. The closure receives its own value, constructed by
calling the type's copy constructor when the closure is
declared. Later changes to the outer binding don't affect the
closure's copy, and vice versa:

```mojo
def main():
    var snapshot = 42

    def frozen() {var snapshot} -> Int:
        return snapshot

    snapshot = 999
    print(frozen())  # 42
```

`{var}` (no name) copies every free variable in the body.

The copy constructor runs once per closure declaration.
Capturing a large `List` or `String` by `var` allocates at that
point. Use `read` or `mut` when an independent copy isn't
needed.

## Move capture: `var name^`

<!-- VERIFIED: unified_closures.mojo:260,
     Signatures.cpp:1311-1325 -->

Transfers ownership of `name` into the closure. The outer
binding is consumed; using it after the closure declaration is a
compile error:

```mojo
def main():
    var data: List[Int] = [1, 2, 3]

    def take_data() {var data^}:
        print(data)

    take_data()      # [1, 2, 3]
    # print(data)    # error: 'data' is uninitialized
                     #        after move
```

Move capture skips the copy that `var name` would perform and
is the only way to capture a move-only type by value.

Constraints:

- Only legal after `var` or after a bare (convention-less) name.
- `{read name^}`, `{mut name^}`, and `{ref name^}` are
  rejected.
- A bare `name^` is equivalent to `var name^`. Both produce the
  same `kConventionMove` entry.

## Copyable closures: `var^`

<!-- VERIFIED: unified_closures_mem.mojo:94-103,
     unified_closures.mojo:185, Signatures.cpp:1278-1293 -->

`{var^}` (no name) applies move capture as the default for
every free variable in the body. When every captured type is
`Copyable`, the resulting closure value is also `Copyable`:

```mojo
def main():
    var label = "sensor-1"

    def tag() {var^} -> String:
        return label

    var clone = tag           # closure value copied
    print(tag())              # sensor-1
    print(clone())            # sensor-1
```

Copying the closure invokes the copy constructor of each
captured value. The constructor fires at the assignment, not at
the closure declaration.

Constraints:

- `{var^}` is a default-convention entry. At most one
  default-convention entry per capture list.
- If any captured type is move-only, the closure is `Movable`
  but not `Copyable`.

Comparison with `{var name^}`:

<!-- markdownlint-disable MD013 -->

| Form          | Captured names                | Closure value                         |
|---------------|-------------------------------|---------------------------------------|
| `{var name^}` | Only `name`, by move          | Not `Copyable` by default             |
| `{var^}`      | All referenced names, by move | `Copyable` if captures are `Copyable` |

<!-- markdownlint-enable MD013 -->

## `ref`

<!-- VERIFIED: unified_closures.mojo:239-254,
     MutOrigin/origin_of usage -->

Reference whose mutability is inherited from the outer
binding's origin. The closure doesn't pick `read` or `mut`; it
forwards whatever the origin already carries:

```mojo
def show_mutability(ref items: List[Int]):
    def report() {ref items}:
        comptime if origin_of(items).mut:
            print("mut")
        else:
            print("immut")
    report()

# `xs` uses default `read` convention, immutable reference
def from_read(xs: List[Int]):
    show_mutability(xs)

# `xs` uses `mut` convention, mutable reference
def from_mut(mut xs: List[Int]):
    show_mutability(xs)

def main():
    var nums: List[Int] = [10, 20, 30]
    from_read(nums)   # immut
    from_mut(nums)    # mut
```

`ref` is the only convention that forwards origin information
unchanged. `read` and `mut` create references with a fixed
mutability; `var` removes the origin relationship entirely.

`ref` captures are intended for generic code that must operate
across mutability contexts. In ordinary closures, `read` and
`mut` produce clearer signatures.

## Empty and omitted capture lists

<!-- VERIFIED: parseCaptureList early return,
     Signatures.cpp:1275-1276 -->

`{}` and no capture list at all produce the same result: any
reference to an outer value is rejected:

```text
error: Could not infer capture convention of the captured
       value a
```

Both forms allow a body that uses only its own arguments. The
function then behaves as a plain nested function with no
captures.

`{}` is preferred when the absence of captures is intentional;
the explicit braces make the constraint visible at the
declaration.

## Mixing conventions

<!-- VERIFIED: parseCaptureList loop, Signatures.cpp:1352-1362 -->

Each entry carries its own convention independently:

```mojo
def main():
    var config = "prod"
    var count = 0
    var label = "run-1"

    def process() {read config, mut count, var label}:
        count += 1
        print(config, count, label)

    process()   # prod 1 run-1
    label = "run-2"
    process()   # prod 2 run-1
                # (label was copied at declaration time)
```

A bare name in a mixed list uses `read`, not the convention of
its neighbors:

```mojo
# y is captured as 'read', not 'mut'
def f() {var z, mut x, y}:
    # ...
```

## Default convention

<!-- VERIFIED: captureAllByConvention path,
     Signatures.cpp:1333-1346 -->

A convention keyword without a name sets the default for every
free variable not named explicitly:

```mojo
def main():
    var a = 1
    var b = 2
    var z = "snapshot"

    def mixed() {mut, var z}:
        a += 10           # 'a' uses default: mut
        b += 20           # 'b' uses default: mut
        print(a, b, z)

    mixed()               # 11 22 snapshot
    z = "changed"
    mixed()               # 21 42 snapshot
                          # ('z' was copied at declaration)
```

Rules:

- At most one default-convention entry per list.
- Position within the list isn't significant.
- Trailing commas are accepted.
- The default doesn't apply to names covered by an explicit
  entry. In `{mut, var z}`, the explicit `var z` overrides the
  default for `z`.

## Parametric closures

<!-- VERIFIED: unified_closure_captured_param.mojo,
     unified_closures_params.mojo:90-102 -->

A closure can declare its own compile-time parameter list:

```mojo
def main():
    # The `Intable` trait supports `Int` conversion
    def double[T: Intable](https://mojolang.org/docs/reference/x: T.md) {} -> Int:
        return Int(x) * 2

    print(double[Int](https://mojolang.org/docs/reference/5.md))         # 10
    print(double[Float64](https://mojolang.org/docs/reference/3.4))   # 6
```

The parameter list, capture list, effects, and return type
appear in the same order as on top-level functions:
`name[parameters](https://mojolang.org/docs/reference/arguments.md) effects {captures} -> ReturnType`.

Parameters and captures compose: the closure's parameters are
bound at each call site, while the capture list controls its
relationship to the enclosing scope. Variadic parameters are
also legal (`def closure[*Ts: Coord](https://mojolang.org/docs/reference/*args: *Ts.md)`).

## Effects

<!-- VERIFIED: unified_closures.mojo:50-56, 408-412, 432-438 -->

Effects appear between the argument list and the capture list.

<!-- markdownlint-disable MD013 -->

| Effect     | Form                            | Type example                           |
|------------|---------------------------------|----------------------------------------|
| `raises`   | `(args) raises {captures} -> T` | def (String) raises -> Int             |
| `thin`     | `(args) thin -> T`              | def (T) thin -> U                      |
| `abi("C")` | `(args) abi("C") -> T`          | def (Float64) thin abi("C") -> Float64 |

<!-- markdownlint-enable MD013 -->

`raises` example:

```mojo
def main() raises:
    var y = 2

    def divide(x: Int) raises {var y} -> Int:
        if y == 0:
            raise Error("divide by zero")
        return x // y

    print(divide(10))   # 5
```

`thin` and `abi("C")` apply only to closure *types* used as
function parameters, not to closure declarations. `thin`
describes a non-capturing function type, which is incompatible
with a closure that captures. See [Function
declarations](https://mojolang.org/docs/reference/function-declarations.md).

## Nesting

<!-- VERIFIED: unified_closures.mojo:309-329 -->

Closures can nest inside closures. Each level has its own
capture list. A name captured at one level is visible to inner
levels through their own capture lists:

```mojo
def main():
    var y = 4

    def outer() {var y} -> Int:
        def inner() {var y} -> Int:
            return y
        return inner() + y

    print(outer())   # 8
```

An inner closure can capture an outer closure by name. This is
how nested callbacks compose:

```mojo
def main():
    def make_adder(n: Int):
        def add(x: Int) {var n} -> Int:
            return x + n

        def twice(x: Int) {var add} -> Int:
            return add(add(x))

        print(twice(5))   # ((5 + 3) + 3) = 11
                          # add(add(5)) = add(8) = 11

    make_adder(3)
```

Closures are values and can be used in capture lists. An inner closure that
names an outer closure must declare the same kind of capture (`var`,
`read`, etc.) as it would for any other value.

## Capture-list errors

<!-- VERIFIED: parseCaptureList diagnostics,
     Signatures.cpp:1314-1349 -->

<!-- markdownlint-disable MD013 -->

| Compiler complaint                                                                | Trigger                                                                      |
|-----------------------------------------------------------------------------------|------------------------------------------------------------------------------|
| Transfer sigil `^` without `var` convention                                       | `^` after `mut`, `read`, or `ref`                                            |
| Duplicate default convention                                                      | Two bare convention keywords in one list                                     |
| Unrecognized token in capture position                                            | Token that isn't a convention keyword or name                                |
| Missing comma between entries                                                     | Identifier followed by an unrecognized token                                 |
| Unterminated capture list                                                         | Missing closing `}`                                                          |
| Outer name not covered by capture list                                            | Body references an outer name the capture list doesn't cover                 |
| Use after move capture                                                            | Reference to a name after `{var name^}` consumed it                          |

<!-- markdownlint-enable MD013 -->

## Restrictions

- **No escape.** A closure can't outlive its enclosing scope.
  Returning a closure from its declaring function or storing it
  past the enclosing scope's end isn't supported.
- **No `thin` or `abi("C")` on declarations.** These apply only
  to closure types used as function parameters; a declaration
  with captures can't be `thin`.
- **Trait conformance through captures.** A struct can contain a
  closure-typed field and conform to a trait through it, but
  every method of that trait must be declared `capturing` until
  the capturing effect is removed (see
  `unified_closure_structs.mojo`).
