Interfaces · Lesson 12.10

Indexer

Source
Read and write a type of your own with square brackets by declaring func [] and func []=.

Square brackets are an operator too. A type that declares func [] can be read as week[day], and one that also declares func []= can be written as week[day] = 21. They are declared in an extend block, just like + and == in Operator overload. The index need not be a number: here a week of temperatures is indexed by the day itself.

The type underneath

// The highest temperature of each day, stored in an ordinary array.
struct Week {
    highs: int32[7];
}

A Week is an ordinary array in a struct. The indexer is what lets a reader write week[Day::Friday] instead of week.highs[4], and lets the type decide which indexes exist.

Reading and writing

// Reading: `week[day]` calls this and gives back a copy of the value.
func [](self: &Week, day: Day) -> int32 {
    return self.highs[day as uint];
}

// Writing: `week[day] = value` calls this. It changes the week, so it takes `&var` self.
func []=(self: &var Week, day: Day, value: int32) {
    self.highs[day as uint] = value;
}

day as uint turns the enum case into its position, 0 for Monday through 6 for Sunday, as in Enum value. The two functions split the work like this:

You writeThe compiler callsReceiverGets
week[Day::Friday][]self: &Weekthe day
week[Day::Friday] = 25[]=self: &var Weekthe day and the value 25

Writing changes the week, so []= takes a &var receiver, as a mutating method does, and week must be declared with var.

var week = Week { highs: [0; 7] };

week[Day::Monday] = 18;
week[Day::Tuesday] = 21;
week[Day::Friday] = 25;

A day nobody wrote, such as Sunday, still holds the 0 it started with.

An index that cannot be wrong

Because the index is a Day, an index the week does not have cannot even be written. week[9] is not an out-of-range day caught while the program runs — it is refused when the program is compiled, because a plain number is not a Day. The type of the index is part of the design.

Two separate functions

// Reading and writing are two separate functions, so changing a value in place takes both.
week[Day::Friday] = week[Day::Friday] - 3;
flowchart LR
    read["week[Day::Friday]<br/>calls [] → 25"] --> sub["25 - 3 = 22"]
    sub --> write["week[Day::Friday] = 22<br/>calls []="]

The right-hand side reads through [], and the assignment writes through []=. Neither function both reads and writes, which is why the shorter week[Day::Friday] -= 3 is refused (see below).

The program

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

Src/Main.rux
// Square brackets are an operator too. A type that declares `func []` can be read as `week[day]`,
// and one that also declares `func []=` can be written as `week[day] = 21`. They are declared in
// an `extend` block like `+` and `==` were.
//
// The index need not be a number. Here a week of temperatures is indexed by day, so a reader sees
// `week[Day::Friday]`, and an index the week does not have, such as 9, cannot even be written:
// a plain number is not a `Day`.
import Io::PrintLine;

enum Day {
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

// The highest temperature of each day, stored in an ordinary array.
struct Week {
    highs: int32[7];
}

extend Week {
    // Reading: `week[day]` calls this and gives back a copy of the value.
    func [](self: &Week, day: Day) -> int32 {
        return self.highs[day as uint];
    }

    // Writing: `week[day] = value` calls this. It changes the week, so it takes `&var` self.
    func []=(self: &var Week, day: Day, value: int32) {
        self.highs[day as uint] = value;
    }
}

func Main() -> int {
    var week = Week { highs: [0; 7] };

    week[Day::Monday] = 18;
    week[Day::Tuesday] = 21;
    week[Day::Friday] = 25;
    PrintLine("Monday {}, Tuesday {}, Friday {}", week[Day::Monday], week[Day::Tuesday],
              week[Day::Friday]);

    // Reading and writing are two separate functions, so changing a value in place takes both.
    week[Day::Friday] = week[Day::Friday] - 3;
    PrintLine("Friday, corrected: {}", week[Day::Friday]);

    // A day nobody wrote still holds the zero it started with.
    PrintLine("Sunday {}", week[Day::Sunday]);

    // Writing `week[Day::Friday] -= 3` is refused, because no single function both reads and
    // writes: "operator '-=' cannot read and write through the '[]' operator on 'Week' at once".
    return 0;
}

Run it

cd Examples/Interfaces/Indexer
rux run
Monday 18, Tuesday 21, Friday 25
Friday, corrected: 22
Sunday 0

Common mistakes

A compound assignment through the brackets.
week[Day::Friday] -= 3 fails with error: operator '-=' cannot read and write through the '[]' operator on 'Week' at once. Spell it out as week[Day::Friday] = week[Day::Friday] - 3.
Indexing with the wrong type.
week[9] fails with error: no '[]' on 'Week' accepts an index of type 'int'. The indexer takes a Day, so write week[Day::Sunday].
Writing to a let value.
Declare week with let and every assignment fails with error: cannot modify immutable variable 'week'. []= takes &var self, so the variable must be a var. Reading through [] still works on a let.

Try it yourself

  1. Add func Warmest(self: &Week) -> int32 that returns the highest of the seven temperatures.
  2. Make []= refuse temperatures above 60 by leaving the old value in place.
  3. Write a Scores type indexed by a Player enum of your own, with both [] and []=.

Learn more