Sequences · Lesson 5.1

Array

Source
Store a fixed number of values inline in an int32[4], index them from zero, and see that copying an array copies every element.
You'll need: Mutable, For

An array holds a fixed number of values of one type, side by side. Four primes, twelve monthly totals, the eight squares of a chess row — whenever you have several values of the same kind and know how many, an array keeps them under one name.

The type says how many

int32[4] means "four int32 values": the element type, then the count in square brackets. An array literal lists the elements, also in square brackets:

let primes: int32[4] = [2, 3, 5, 7];

The count is part of the type. An int32[4] and an int32[5] are different types, and an array never grows or shrinks. The elements live inline, right inside the variable — exactly like the bytes of a single integer do. Nothing is allocated, and nothing has to be freed.

Indexing from zero

Elements are numbered from zero, so the last one sits at length - 1:

Index0123
primes[...]2357
PrintLine("first {}, last {}", primes[0], primes[primes.length - 1]);
PrintLine("length {}", primes.length);

Walking an array

for walks an array element by element, just as it walked a range:

var sum: int32 = 0;
for prime in primes {
    sum += prime;
}

When the position matters, walk the indices instead. .. stops before its upper bound, which is exactly the set of valid indices:

for i in 0..primes.length {
    PrintLine("primes[{}] = {}", i, primes[i]);
}

Changing and copying

let freezes the elements too. To change one, the array must be var:

var scores: int32[3] = [10, 20, 30];
scores[1] = 25;

Assigning an array to another variable copies all of its elements. The two variables share nothing, so changing the copy leaves the original alone:

var copy = scores;
copy[0] = 99;
flowchart LR
    s["scores<br/>10 · 25 · 30"] -- "var copy = scores;" --> c["copy<br/>10 · 25 · 30"]
    c -- "copy[0] = 99;" --> c2["copy<br/>99 · 25 · 30"]
    s -. "unchanged" .-> s2["scores<br/>10 · 25 · 30"]

Two arrays of the same type are equal when every element is, so scores == copy is false once one element differs.

Every index is checked

An index must be below length. A constant index past the end does not compile. An index computed while the program runs is checked as it runs, and one past the end stops the program with Panic: index out of range — it never reads the memory beyond the array.

The program

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

Src/Main.rux
// An array holds a fixed number of values of one type, side by side. `int32[4]` is "four int32
// values": the element type, then the count in square brackets. The count is part of the type,
// so an `int32[4]` and an `int32[5]` are different types, and an array never grows or shrinks.
//
// The elements live inline, right inside the variable, exactly like the bytes of a single
// integer do. Nothing is allocated, and copying an array copies every element.
import Io::PrintLine;

func Main() -> int {
    // An array literal lists the elements in square brackets.
    let primes: int32[4] = [2, 3, 5, 7];

    // Elements are numbered from zero, so the last one sits at `length - 1`.
    PrintLine("first {}, last {}", primes[0], primes[primes.length - 1]);
    PrintLine("length {}", primes.length);

    // `for` walks an array element by element, just as it walked a range.
    var sum: int32 = 0;
    for prime in primes {
        sum += prime;
    }
    PrintLine("sum {}", sum);

    // When the position matters, walk the indices instead. `..` stops before its upper bound,
    // which is exactly the set of valid indices.
    for i in 0..primes.length {
        PrintLine("primes[{}] = {}", i, primes[i]);
    }

    // `let` freezes the elements too. To change one, the array must be `var`.
    var scores: int32[3] = [10, 20, 30];
    scores[1] = 25;

    // Assigning an array copies all of its elements. Changing the copy leaves the original
    // alone, because the two variables share nothing.
    var copy = scores;
    copy[0] = 99;
    PrintLine("scores {} {} {}", scores[0], scores[1], scores[2]);
    PrintLine("copy   {} {} {}", copy[0], copy[1], copy[2]);

    // Two arrays of the same type are equal when every element is.
    PrintLine("scores == copy  {}", scores == copy);

    // Every index must be below `length`, and it is checked. A constant index past the end,
    // `primes[7]`, does not compile:
    //     error: index 7 is out of range for an array of 4 elements
    // An index computed while the program runs is checked as it runs, and one past the end stops
    // the program with `Panic: index out of range` rather than read memory beyond the array.
    return 0;
}

Run it

cd Examples/Sequences/Array
rux run
first 2, last 7
length 4
sum 17
primes[0] = 2
primes[1] = 3
primes[2] = 5
primes[3] = 7
scores 10 25 30
copy   99 25 30
scores == copy  false

Common mistakes

Changing an element of a let array.
primes[0] = 1; fails with error: cannot modify immutable variable 'primes', and the compiler suggests declaring it with var.
Counting to length inclusive.
for i in 0..=primes.length visits one index too many. The last pass stops the program with Panic: index out of range. Use 0..primes.length. With a constant index the compiler catches it first: primes[7] fails with error: index 7 is out of range for an array of 4 elements and help: valid indexes are 0 through 3.
A literal with the wrong number of elements.
let three: int32[4] = [1, 2, 3]; fails with error: cannot assign 'int[3]' to 'int32[4]'. The count is part of the type, so it has to match.
Printing a whole array.
PrintLine("{}", primes) is refused — an array is not printable as one value. Print its elements, as the program does.

Try it yourself

  1. Find the largest element of primes with a for loop and a var.
  2. Print the elements of primes in reverse order by walking the indices backwards.
  3. Set copy[0] back to 10 and print scores == copy again.
  4. Change 0..primes.length to 0..=primes.length and run the program.

Learn more