Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tuples

(a, b) creates a tuple; (value,) creates a one-component tuple. Parentheses without a comma group an expression, and empty () is unit. Annotations accept tuple[A, B] or (A, B). A one-component type requires (A,) or tuple[A]; (A) groups the type A, just as parentheses group an expression.

Tuples are structural products with fixed component types. Construction allocates nothing; component expressions retain their own allocation and error contracts. Tuples containing owned components move, while copyable tuples copy their components. Resolved owned closures can be stored; references and generators cannot.

A mut tuple binding permits mutable component borrows regardless of whether its components are copyable. A copied tuple has independent inline storage; changing the original does not change the copy. Components can be changed through &mut pair[0] or a mutable payload match. Direct indexed assignment (pair[0] = value) remains unsupported; assign through the reference instead.

pair[0] selects a component. Indices must be integer literals. A negative literal counts from the end, as in Python: for a tuple of length n, pair[-k] is pair[n - k], and pair[-0] is pair[0]. The same resolved component is borrowed by &pair[-1] and &mut pair[-1]. An index outside -n to n - 1 is a compile error that states the tuple’s length. Owned components may be observed or explicitly copied; moving them out requires whole-tuple unpacking:

number, text = (42, "cm")
number, text = 42, "cm"

Recursive unpacking transfers each selected component; _ destroys a discarded component. The parentheses are optional on the right of an unpacking statement, and targets may also be fields and collection elements: see assignment to several targets.

Unpacking a tuple reference borrows the components in place instead. With p: &(i64, str), n, s = p binds n = &p[0] and s = &p[1]; with a &mut reference, or n, s = &mut pair, each name is a &mut reference to its component, and _ skips one. The names are new immutable bindings: they cannot reuse existing names or be declared mut. for n, s in &pairs unpacks each element the same way, as described under iterable values.

def bump(p: &mut (i64, str)) -> Result[(), OverflowError[i64]]:
    n, _ = p
    *n += 1
    Ok(())

def main() -> Result[(), AllocError | IoError | OverflowError[i64]]:
    mut pair = (1, "one")
    bump(&mut pair)?
    n, s = &pair
    print(*n, s)?
    Ok(())
2 one

A slice with integer-literal bounds and step selects components at compile time: (1, "two", 3.0)[1:] has type (str, f64), and pair[::-1] reverses a pair. It allocates nothing, so it returns no Result and takes no ?. Slices of affine components are rejected for now.

Nested tuple targets mirror the value’s shape: ((x, y), label) = value. Parentheses without a comma group a target; (x,) requires one component. The same shapes work for owned and borrowed loop elements, comprehension bindings and generator-expression bindings. Borrowing a nested tuple keeps component-precise loans, so sibling components remain independent. Starred unpacking is unsupported. Unit components can be discarded or used as unit expressions without enabling standalone unit storage.

An owned list also supports unpacking with an exact runtime length check at each list position in a nested target; tuple positions use their statically known arity. See collection rules. Tuples of integers, bool, strings, and nested tuples of those types are copyable dictionary keys and set members. Hashing and equality use their contents, with no formatting or string-key encoding.

Tuples whose components are integers, floats, strings, or recursively orderable tuples support <, <=, >, and >=. Both operands must have exactly the same type. They compare lexicographically, using the first differing component, with the same allocation-free total order as list.sort(). This also works through references and in generic functions; comparison observes both operands. Booleans, unit, classes, ordinary enums, and collections are not orderable fields.

Floating-point tuple fields use the sorting order: negative zero precedes positive zero, and all NaNs compare equal in order and follow every number. Structural == and != still use IEEE equality. Consequently, (-0.0,) == (0.0,) and (-0.0,) < (0.0,) are both true; a NaN tuple is <= itself but not == itself. Scalar float operators keep their IEEE behavior.

The difference is deliberate. Tuple ordering exists to sort and rank records, so it must be total: every pair of tuples is ordered, and < on tuples agrees with sort(). IEEE ordering would make a tuple containing NaN unordered against every other tuple. Plenty has one total order rather than separate partial and total orders; the ordering proposal gives the rationale.

Distinct components of a named tuple can be borrowed independently. Reads of one component can continue while another is exclusively borrowed; whole-tuple access overlaps all components. See borrowing for projection and collection-element loan rules.