Skip to content

Tuples ​

A tuple is a fixed-size value that holds a known number of elements, each with its own type. Use tuples to return, pass, unpack, and pattern-match a small group of values. Tuples are not variable-size collections.

Grammar ​

The normative productions are in Complete Grammar:

ebnf
tuple-expression = "(", expression, ",", ")"
                 | "(", expression, ",", expression,
                   { ",", expression }, ")" ;

tuple-type = "(", type, ",", ")"
           | "(", type, ",", type, { ",", type }, ")" ;

unpack-target
    = binding-target, ",", binding-target, { ",", binding-target }
    | "(", binding-target-list, ")" ;

binding-target-list
    = binding-target, ","
    | binding-target, ",", binding-target, { ",", binding-target } ;

binding-target
    = identifier
    | "*", identifier
    | "(", binding-target-list, ")" ;

tuple-pattern = "(", pattern, ",", ")"
              | "(", pattern, ",", pattern, { ",", pattern }, ")" ;

rest-pattern = "*", ( identifier | "_" ) ;

Tuple values are always parenthesized:

  • (value) is grouping.
  • (value,) is a singleton tuple.
  • () is not a tuple value.
  • A multi-element tuple may end with a trailing comma: (1, 2,) is (1, 2).
Aura
def main():
    pair = ("north", 7)
    singleton = (true,)
    nested = (pair, (2, 3))

Top-level assignment and for binding lists use left, right. Inside a target, parentheses mark a nested target or a singleton target. Tuple types and tuple patterns are parenthesized.

Typing Rules ​

A tuple type records one exact element type at each position:

Aura
def location() -> (str, int64):
    return ("north", 7)

point: (int64, int64) = (3, 4)

When an expected tuple type is present, the tuple expression's arity and element types must match it exactly. Otherwise each element is inferred in its own position.

Tuple types are structural. Two tuple types are equal exactly when they have the same arity and equal corresponding element types.

Equality. Tuple == and != require both operands to have the same static tuple type. Equality compares corresponding element values recursively. When one operand is a tuple literal and the other has a known tuple type, that exact type contextually types the literal, recursively. This rule is symmetric.

<, <=, >, and >= are not defined for tuples. Aura does not infer a lexicographic ordering.

Optional tuples. An optional tuple is the ordinary union of a complete tuple type with None: (str, int64) | None.

indirect. indirect tuple types are rejected. indirect is the facility for recursive named fields. So a class field cannot place its recursive link inside a tuple. Put that link in a separately named indirect field instead. The compiler's diagnostic for the tuple case points to that fix.

Unpacking and patterns. An unpacking target or tuple pattern must have the scrutinee's exact recursive tuple shape. Each binding leaf receives its corresponding element type.

Rest elements. One *name element in a target or tuple pattern takes the positions the other elements leave over. It binds them as a tuple of their element types. *_ ignores them.

Aura
def main():
    head, *tail = (1, "two", 3.0)
    print(head)
    print(tail)
    first, *_, last = (1, 2, 3, 4)
    print(first + last)

This prints 1, (two, 3.0), and 5. The fixed elements must not outnumber the tuple positions. Aura has no empty tuple, so *name must cover at least one position, while *_ may cover none. A second rest element, or a rest that is the whole target, is rejected with AU2041. The ordinary binding rules reject duplicate names and a leaf that shadows a visible name. A tuple binding leaf is a name, not a member or index place.

Indexing. A tuple index must be a non-negative integer literal known at compile time. It must select an existing position, and that element's type must be copyable. The expression has the selected element's type. These are static errors:

  • a computed index
  • a negative literal
  • an out-of-bounds literal
  • selection of a non-copy element

Runtime Semantics ​

A tuple value stores its elements in source order. Construction evaluates and captures each element from left to right.

An unpacking operation evaluates its right side or iteration item exactly once. It then binds leaves left to right, following the recursive tuple shape.

A tuple-pattern match evaluates the scrutinee once and tests arms in source order. The first matching arm executes. A tuple pattern is irrefutable when all nested patterns are binding patterns or _. Literal and enum subpatterns keep their usual matching and exhaustiveness rules.

Constant tuple indexing selects the statically named position and returns a copy. There is no runtime index expression to evaluate.

Equality. Tuple == compares corresponding element values from left to right, using each element type's ordinary equality. Nested tuples follow the same rule recursively. The result is true only when every corresponding comparison is true. Comparison stops at the first unequal element. Tuple != is the logical negation of tuple ==.

Both complete operand expressions are evaluated once, left to right. The comparison reads the two resulting tuple values and consumes neither, even when an operand contains non-copy elements. Evaluating an operand expression still has its ordinary ownership effects. The equality operation itself adds no move.

A tuple value may carry runtime element-type, transport, or backend metadata. That metadata is not an additional equality component, because the checker has already required one common static tuple type.

Tuple equality links follow the ordinary comparison-chain contract. For example, first == middle != last evaluates first, then middle, and compares the first link. It evaluates last only when that link is true. Each evaluated operand, including middle, is evaluated once. Tuple ordering is a static error.

Rendering. A rendered tuple uses parentheses, , between elements, and one final comma for a singleton: (1, 2) and (1,). Each element uses its ordinary Aura rendering, so a contained str is not quoted. print, f-string interpolation, and backend diagnostics all use this format. Rendering plays no part in tuple equality and does not define tuple ordering.

Aura
def make_record() -> (str, int64):
    return ("Aura", 7)

def main():
    record = make_record()
    assert record == ("Aura", 7)
    assert record != ("Aura", 8)
    name, version = record
    print(name)
    print(version)

    copy_pair = (10, 20)
    print(copy_pair[1])

    for label, count in [("ready", 2), ("done", 3)]:
        print(f"{label}:{count}")

    nested = ((1, 2), true)
    assert nested == ((1, 2), true)
    assert nested != ((1, 3), true)
    assert (1, 2) == (1, 2) != (2, 1)
    match nested:
        case ((left, right), flag):
            print(left + right)
            print(flag)
text
Aura
7
20
ready:2
done:3
3
true

Ownership And Evaluation Order ​

A tuple is copyable if and only if every element type is copyable. Assignment, owned argument passing, returns, and pattern flow then follow the ordinary copy or move rule for the tuple as a whole.

Construction. A tuple literal moves each non-copy element into the new tuple, even when the tuple goes to a shared position such as a print argument. See Operations That Move.

Unpacking. Unpacking a copy tuple copies its elements and leaves the source usable. Unpacking a non-copy tuple consumes the whole source exactly once and gives owned leaf bindings. Aura does not turn positional fields into independently reusable partial-move places. Any later use of the source is diagnosed as use after move.

Equality. Tuple == and != are shared reads. They do not unpack or transfer ownership. Both operands stay usable, including a non-copy tuple such as (str, int64).

Iteration. In collection iteration, tuple leaves inherit the ownership provenance of the yielded element:

  • bare shared iteration retains the collection and gives shared leaf provenance for non-copy tuple elements
  • own iteration consumes the collection and gives owned leaves
  • bare Queue iteration receives an owned tuple item and gives owned leaves

Mutable-borrow iteration with a tuple target is rejected. Aura does not rebuild a recursively unpacked tuple and write it back into a collection element.

Matching. match own consumes a non-copy tuple scrutinee and gives owned leaf bindings. Bare match retains the tuple and gives shared leaf provenance. match mut with a tuple pattern is rejected, because tuples do not support mutable tuple-pattern writeback.

Diagnostics ​

CodeCause
AU1101a malformed tuple expression, type, target, or pattern, or misplaced commas
AU2002an annotated tuple element-type mismatch
AU2041a misplaced or repeated rest element, too few tuple positions for the fixed elements, or a *name that covers no positions
AU2999a tuple shape or arity mismatch, reported with the checker's general code
AU3001using a non-copy tuple after whole-source unpacking or match own, reported at the move
AU3002trying to move an element through shared unpacking or bare match

Unsupported tuple operations are rejected at check time. These include non-constant or invalid indexing and mutable tuple writeback forms. The diagnostic names the restriction and the supported alternative.

Backend Support ​

Both backends implement tuples: MIR execution and direct native generation. MIR is the compiler's mid-level intermediate representation. The implemented surface covers:

  • tuple construction and fixed structural types
  • function returns
  • recursive assignment and loop unpacking
  • tuple patterns
  • rest elements in targets and tuple patterns
  • whole-source ownership
  • copy-only constant indexing
  • recursive structural equality

Maintained parity fixtures require both backends to produce the same output and primary diagnostics.

Limits And Implementation-Defined Behavior ​

Aura 0.6 does not have these tuple features:

  • an empty tuple
  • tuple iteration
  • tuple methods
  • tuple ordering
  • named tuple elements
  • mutable tuple-target writeback
  • tuple slicing
  • dynamic tuple indexing

A tuple is not implicitly converted to or from list.

These behaviors are language-defined, not implementation-defined:

  • tuple element order
  • left-to-right construction
  • recursive shape matching
  • copy classification
  • whole-source non-copy moves
  • constant-index results
  • recursive equality

Runtime tuple metadata cannot change the equality result.

Status ​

Tuples are implemented as described on this page:

  • parenthesized tuple values and types
  • function returns
  • recursive assignment and for unpacking
  • recursive tuple patterns
  • rest elements in unpacking targets and tuple patterns
  • structural copy classification
  • whole-source moves
  • shared borrowed destructuring
  • copy-only constant indexing
  • same-static-type recursive == and !=

The limits above are intentional parts of the design.

Design record: ADR-0026.

Aura 0.6.0 technical preview. Implementation baseline: 4d7e076de9ff6e2e056cdec10881d4bf421358de.