The primary composite type involves simple aggregation of other types as a tuple, called a "product type" in formal type theory:
fn DoubleBoth(x: i32, y: i32) -> (i32, i32) {
return (2 * x, 2 * y);
}
This function returns a tuple of two integers represented by the type
(i32, i32). The expression to return it uses a special tuple syntax to build a
tuple within an expression: (<expression>, <expression>). This is actually the
same syntax in both cases. The return type is a tuple expression, and the first
and second elements are expressions referring to the i32 type. The only
difference is the type of these expressions. Both are tuples, but one is a tuple
of types.
Element access uses a syntax similar to field access, with an element index instead of a field name:
fn Sum(x: i32, y: i32) -> i32 {
var t: (i32, i32) = (x, y);
return t.0 + t.1;
}
A parenthesized template constant expression can also be used to index a tuple:
fn Choose(template N:! i32) -> i32 {
return (1, 2, 3).(N % 3);
}
A tuple type Source can be converted to a tuple type Dest if they have the
same number of elements, and each element type of Source is convertible to the
corresponding element type of Dest, and the conversion is implicit if all of
the element type conversions are implicit. See
here for full details.
() is the empty tuple. This is used in other parts of the design, such as
functions, where a type with a single value is needed.
The final element in a tuple literal may be followed by a trailing comma, such
as (1, 2,). This trailing comma is optional in tuples with two or more
elements, and mandatory in a tuple with a single element: (x,) is a one-tuple,
whereas (x) is a parenthesized single expression.
A tuple of types can be used in contexts where a type is needed. This is made
possible by a built-in implicit conversion: a tuple can be implicitly converted
to type type if all of its elements can be converted to type type, and the
result of the conversion is the corresponding tuple type.
For example, (i32, i32) is a value of type (type, type), which is not a type
but can be implicitly converted to a type. (i32, i32) as type can be used to
explicitly refer to the corresponding tuple type, which is the type of
expressions such as (1 as i32, 2 as i32). However, this is rarely necessary,
as contexts requiring a type will implicitly convert their operand to a type:
// OK, both (i32, i32) values are implicitly converted to `type`.
fn F(x: (i32, i32)) -> (i32, i32);
Like some other aggregate data types like struct types, there are some operations are defined for tuples field-wise:
For binary operations, the two tuples must have the same number of components and the operation must be defined for the corresponding component types of the two tuples.
Tuple values can be matched using a tuple pattern, which is written as a tuple of element patterns:
let tup: (i32, i32, i32) = (1, 2, 3);
match (tup) {
case (a: i32, 2, var c: i32) => {
c = a;
return c + 1;
}
}
Tuples could support multiple indices and slicing to restructure tuple elements:
fn Baz(x: i32, y: i32, z: i32) -> (i32, i32) {
var t1: (i32, i32, i32) = (x, y, z);
var t2: (i32, i32, i32) = t1.((2, 1, 0));
return t2.(0 .. 2);
}
This code would first reverse the tuple, and then extract a slice using a half-open range of indices.
The intent of 0 .. 2 is to be syntax for forming a sequence of indices based
on the half-open range [0, 2). There are a bunch of questions we'll need to
answer here:
... perhaps, unclear if that ends up
aligned or in conflict with other likely uses of ... in pattern
matching)?0.2, is that similarity of
syntax OK?
.. to be surrounded by whitespace to
minimize that collision?type