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.