Sequences · Lesson 5.7

Tuple

Source
Group values without declaring a type, (int32, bool), reach the members with .0 and .1, and return several results from one function.
You'll need: Slice, Return

A tuple groups a few values without declaring a type for them first. Its type is the list of its members' types in parentheses — (int32, bool) — and a value is written the same way, (17, true). Arrays hold many values of one type; a tuple holds a fixed handful of values of any types, side by side.

Writing and reading a tuple

The members are reached by position: .0 is the first, .1 the second, and so on:

let pair: (int32, float64) = (3, 2.5);
PrintLine("pair.0 is {} and pair.1 is {}", pair.0, pair.1);
CollectionElement typesHow manyReached by
Arrayall the samefixed, any numbervalues[i]
Tupleeach its ownfixed, usually fewpair.0, .1

Like an array, a tuple is a plain value: assigning it copies every member, and let freezes the members too.

Several results from one function

The everyday use is returning more than one result. A function has a single return type, and a tuple lets that one type carry several values. Divide returns the quotient and whether it means anything — there is no number to return for a division by zero, so the second member says whether the first is real:

func Divide(numerator: int32, denominator: int32) -> (int32, bool) {
    if denominator == 0 {
        return (0, false);
    }
    return (numerator / denominator, true);
}
flowchart LR
    a["`Divide(17, 5)`"] --> ra["`(3, true)`<br/>`.0` is the answer"]
    b["`Divide(1, 0)`"] --> rb["`(0, false)`<br/>`.0` means nothing"]

The caller checks the second member before trusting the first:

let good = Divide(17, 5);
if good.1 {
    PrintLine("17 / 5 is {}", good.0);
}

The members need not share a type. Smallest returns the smallest value, an int32, and its position, a uint:

func Smallest(values: int32[..]) -> (int32, uint) {

Comparing tuples

Tuples of the same type compare member by member: equal when every member is equal. A tuple literal takes its member types from the other side, just as a lone 3 would:

PrintLine("good == (3, true)  {}", good == (3, true));

A tuple inside a tuple

A member may itself be a tuple. The indexes chain, so outer.0.1 is the second member of the first member:

let outer: ((int32, int32), char8[..]) = ((3, 4), "point");
PrintLine("{} at {}, {}", outer.1, outer.0.0, outer.0.1);

Once a tuple's members need names to be understood — or it has more than two or three of them — it is time for a struct, or at least for the next lesson, Destructure.

The program

The whole lesson is one package in the Examples repository. Its comments explain every step.

Src/Main.rux
// A tuple groups a few values without declaring a type for them first. Its type is the list of
// its members' types in parentheses — `(int32, bool)` — and a value is written the same way,
// `(17, true)`. The members are reached by position: `.0` is the first, `.1` the second.
//
// The everyday use is returning more than one result from a function. A function has a single
// return type, and a tuple lets that one type carry several values.
import Io::PrintLine;

// Two results from one call: the quotient, and whether it means anything. There is no number to
// return for a division by zero, so the second member says whether the first is real.
func Divide(numerator: int32, denominator: int32) -> (int32, bool) {
    if denominator == 0 {
        return (0, false);
    }
    return (numerator / denominator, true);
}

// The members need not share a type.
func Smallest(values: int32[..]) -> (int32, uint) {
    var smallest = values[0];
    var position: uint = 0;
    for i in 1..values.length {
        if values[i] < smallest {
            smallest = values[i];
            position = i;
        }
    }
    return (smallest, position);
}

func Main() -> int {
    // Written out in full: the type, then the value.
    let pair: (int32, float64) = (3, 2.5);
    PrintLine("pair.0 is {} and pair.1 is {}", pair.0, pair.1);

    // Check the second member before trusting the first.
    let good = Divide(17, 5);
    if good.1 {
        PrintLine("17 / 5 is {}", good.0);
    }
    let bad = Divide(1, 0);
    if !bad.1 {
        PrintLine("1 / 0 has no answer");
    }

    let temperatures: int32[5] = [12, 9, 4, 7, 11];
    let coldest = Smallest(temperatures);
    PrintLine("coldest {} at position {}", coldest.0, coldest.1);

    // Tuples of the same type compare member by member. The literal `(3, true)` takes its member
    // types from the other side, just as a lone `3` would.
    PrintLine("good == (3, true)  {}", good == (3, true));
    PrintLine("bad == (3, true)   {}", bad == (3, true));

    // A tuple may hold another tuple. The indexes chain, so `outer.0.1` is the second member of
    // the first member.
    let outer: ((int32, int32), char8[..]) = ((3, 4), "point");
    PrintLine("{} at {}, {}", outer.1, outer.0.0, outer.0.1);
    return 0;
}

Run it

cd Examples/Sequences/Tuple
rux run
pair.0 is 3 and pair.1 is 2.5
17 / 5 is 3
1 / 0 has no answer
coldest 4 at position 2
good == (3, true)  true
bad == (3, true)   false
point at 3, 4

Common mistakes

Try it yourself

  1. Write MinMax(values: int32[..]) -> (int32, int32) and print both members for temperatures.
  2. Declare var point = (1, 2);, change point.0, and print both members.
  3. Copy a var tuple into a second variable, change the original, and check that the copy kept its old values.
  4. Compare good == (1, 2) and read the error.

Learn more