Cone Language Reference

Tuples: several values at once

On this page

Some answers come in more than one piece. A division gives a quotient and a remainder; a time splits into hours, minutes and seconds. A tuple holds a few values together, each with its own type, with nothing to declare first. This short page shows how a function hands back several values, how the caller catches them, and how to hold, read and swap them. Here it turns the moment Apollo 11 touched down, counted in seconds from launch, into hours, minutes and seconds:

mod landing;

import stdio use *;

// divmod gives back two values: how many times b goes into a,
// and what is left over
fn divmod(a i32, b i32) i32, i32 {
  a / b, a % b;
}

fn main() {
  // Apollo 11 touched down on the Moon this many seconds after launch
  let elapsed = 369940;

  // Declare a variable for each answer, then catch both at once
  let minutes i32;
  let seconds i32;
  minutes, seconds = divmod(elapsed, 60);

  let hours i32;
  let mins i32;
  hours, mins = divmod(minutes, 60);
  print <- "It landed ", hours, " hours, ", mins, " minutes and ", seconds, " seconds after launch\n";
}

It prints:

It landed 102 hours, 45 minutes and 40 seconds after launch

Try this: the crew splashed down in the Pacific 703115 seconds after launch. How long was the whole flight?

Handing back several values#

divmod's return type is a list of types, i32, i32, and its last expression is a list of values, a / b, a % b. Together they are a tuple: one value made of two, handed back as one. The caller takes it apart with one variable for each value, in order, on the left of the =. Each variable's type must match its value's, which is why minutes and seconds are declared first, with their types, and then given their values together. That is the delayed assignment of page two, done for two variables at once.

Nothing needs declaring for the tuple itself. The types are the declaration: a tuple of an i32 and an i32 is the same type wherever it is written, and so is any tuple with the same number of values, of the same types, in the same order.

Parallel assignment#

Giving several variables their values in one statement is parallel assignment, and it has one property that makes it more than shorthand: every value on the right is worked out before any variable on the left changes. So two variables trade their values with no third one to hold either:

var a = 1;
var b = 2;
a, b = b, a;          // a is 2, b is 1

That is the move the Fibonacci closure made on Functions, a, b = b, a + b: the new b is worked out from the old a, not the new one. Fields take part like any variable, p.x, p.y = p.y, p.x, and so may three or more values. For the commonest case, two values trading places, there is also the swap operator: a <=> b.

A tuple in one variable#

A tuple need not be taken apart. A variable may hold the whole of it, and its values, called its elements, are read by their position, counting from 0, after a dot:

var pair (i32, i32) = 7, 3;
print <- "pair is ", pair.0, " and ", pair.1, "\n";      // pair is 7 and 3
pair = pair.1, pair.0;
print <- "swapped, ", pair.0, " and ", pair.1, "\n";     // swapped, 3 and 7

let both = divmod(100, 7);
print <- "100 / 7 is ", both.0, " remainder ", both.1, "\n";   // 100 / 7 is 14 remainder 2

Where the type is written, as it is for a variable, a parameter or a field, the element types go in parentheses: (i32, i32). The elements may be of any types, mixed: a reading might be (i32, bool, f64). A value assigned to the variable must fit it, as many values as it has elements, each fitting its element's type, so a number with no suffix takes its element's number type, and pair = 1, 2, 3 is refused: "Expression's type does not match lval's type". Parentheses around the values change nothing: pair = (9, 8) is the same assignment.

A tuple may hold another, and the dots go on: t.1.0 is element 0 of element 1 of t. A number after the dot is always an element's position, never part of a decimal number. An element is read from a call's result just as well: divmod(100, 7).1 is 2. A tuple whose values are all constants is itself a constant, so it may be a named constant, a global's first value, or a field's default: const Unit (f32, f32) = 1, 1;. And a tuple type used often can be given a name with alias, which is the tuple itself under another name:

alias Pair = (i32, i32);
fn first(a Pair) i32 { a.0; }

When the values deserve names#

A tuple is at its best travelling a short way, from a function to the code that called it, where the variables that catch it give its values their meaning. Kept for longer, it starts to cost: both.0 says nothing about what it holds, and a reader has to remember whether the quotient came first. Nor can a tuple have methods of its own, since a tuple type has no declaration to put them in.

You can now hand several values back from a function, catch them, keep them together and swap them. When the values deserve names, and the group deserves methods of its own, it is time for a struct: a tuple whose values have names, which is the next page.