3.14. Tuple
Tuples are a concise syntax to create anonymous data structures.
A tuple type is declared with the tuple keyword followed by a list of element types
(optionally named) in angle brackets:
tuple<int; float> // unnamed elements
tuple<i:int; f:float> // named elements
Tuple field names are part of the type. Two tuple declarations are the same only if they have the same number of elements, the same element types, and the same field names (in the same positions). An unnamed tuple is not assignable to a named tuple, and a named tuple is not assignable to a tuple with different names — even when the element types match:
var a : tuple<int; float>
var b : tuple<i:int; f:float>
var c : tuple<x:int; y:float>
// a = b // error: tuple<int;float> is not the same type as tuple<i:int;f:float>
// b = c // error: tuple<i:int;f:float> is not the same type as tuple<x:int;y:float>
var d : tuple<i:int; f:float>
b = d // ok — same names, same types
The same rule applies to construction: a bare positional literal (1, 2.0)
produces an unnamed tuple<int;float> and is not accepted where a named
tuple type is expected. Use the named-field literal form to construct a named
tuple directly:
var b : tuple<i:int; f:float> = (i = 1, f = 2.0) // ok
// var b : tuple<i:int; f:float> = (1, 2.0) // error: not the same type
Mixing named and positional fields in the same literal is not supported — either every field is named or none are.
Tuple elements can be accessed via nameless fields, i.e. _ followed by the 0 base field index:
a._0 = 1
a._1 = 2.0
Named tuple elements can be accessed by name as well as via nameless field:
b.i = 1 // same as _0
b.f = 2.0 // same as _1
b._1 = 2.0 // _1 is also available
Tuples follow the same alignment rules as structures (see Structures).
Tuple alias types can be constructed the same way as structures. For example:
tuple Foo {
a : int
b : float
}
It’s the same as:
typedef Foo = tuple<a:int,b:float>
Tuples can be constructed using the tuple constructor, for example:
var a = (1,2.0,"3")
var b = tuple(1, 2.0, "3")
The => operator creates a 2-element tuple from its left and right operands:
var c = "one" => 1 // same as tuple<string,int>("one", 1)
This works in any expression context, not just table literals.
Table literals like { "one"=>1, "two"=>2 } use => to form key-value tuples
that are then inserted into the table (see Tables).
Tuple elements can be assigned names via tuple constructor:
var a = tuple<a:int,b:float,c:string>(a=1, b=2.0, c="3")
Both auto and a full type specification can be used to construct a tuple.
Array of tuples can be constructed using similar syntax, with a comma as a separator:
let H : array<Tup> <- array tuple<Tup>((a = 1, b = 2., c = "3"), (a = 4, b = 5., c = "6"))
Tuples can be expanded upon the variable declaration, for example:
var (a, b, c) = (1, 2.0, "3")
In this case only one variable is created, as well as for ‘assume’ expressions. I.e:
var a`b`c = (1, 2.0, "3")
assume a = a`b`c._0
assume b = a`b`c._1
assume c = a`b`c._2
Iterators and containers can be expanded in the for-loop in a similar way:
var H <- [(1, 2.0, "3"), (4, 5.0, "6")]
for ( (a, b, c) in H ) {
assert(a == 1)
assert(b == 2.0)
assert(c == "3")
}
3.14.1. Passing tuples as arguments — const widening
When a tuple value is passed as a function argument, each pointer field
inside the tuple participates in the same const-widening rule used for
top-level pointer parameters (see Pointers).
A non-const pointer T? inside the argument tuple is accepted where the
parameter tuple has T const?:
struct Node { at : Loc }
var ats : array<tuple<string; Loc const?>>
// Exact-match overload.
def takeng(a : array<tuple<string; Loc const?>>;
b : tuple<string; Loc const?>) { ... }
// Generic overload — TT is inferred from the array element type,
// so b must match tuple<string; Loc const?> as well.
def take(a : array<auto(TT)>; b : TT) { ... }
def feed(var a : Node?&) {
take(ats, ("test", unsafe(addr(a.at)))) // tuple<string; Loc?>
takeng(ats, ("test", unsafe(addr(a.at)))) // accepted for
// tuple<string; Loc const?>
}
The widening is one-directional (T? widens to T const?, not the
reverse) and applies to tuples that appear directly as an argument type.
Tuples nested inside containers or inside other structures are not relaxed —
their element types must match exactly. Variants and options do not
participate in this relaxation.
The implementation lives in TypeDecl::isSameType in
src/ast/ast_typedecl.cpp: the isPassType flag is propagated into the
tuple’s argTypes comparison so the pointer field inherits the same
relaxation that the top-level parameter pointer gets.
See also
Datatypes for a list of built-in types,
Pattern matching for matching and destructuring tuples,
Finalizers for tuple finalization,
Move, copy, and clone for tuple copy and move rules,
Aliases for the typedef shorthand tuple syntax,
Pointers for the matching rule on top-level pointer arguments.