Ranges

A range is an interval of numbers written with .., ..= or .... It is an ordinary value with a type of its own, and it has three everyday jobs: driving a for loop, cutting a slice out of a sequence, and matching an interval in a pattern.

for i in 0..5 {
    PrintLine("{}", i);   // 0, 1, 2, 3, 4
}

The six forms

The start, when there is one, is always included. The end is excluded by .. and included by ..=. Either bound may be left out:

ExpressionTypeContainsMembers
1..4int..int1, 2, 3.start, .end
1..=4int..=int1, 2, 3, 4.start, .end
1..int..1 and every value after it.start
..4..intevery value before 4.end
..=4..=intevery value up to 4, inclusive.end
....everythingnone
range-expression = expression ( ".." | "..=" | "..." ) expression
                 | expression ".."
                 | ( ".." | "..=" | "..." ) expression
                 | ".."
range-type       = type ( ".." | "..=" ) type
                 | type ".."
                 | ( ".." | "..=" ) type
                 | ".."

In expressions and patterns ... is a second spelling of the inclusive ..=: 1...4 is 1..=4, and ...4 is ..=4. Types use ..= only. (After an argument, values... before ) or , is a spread, not a range — see Slices.)

A range type is written with the same punctuation as its values, so a range can be stored, passed and returned like any other value:

func Width(span: int..=int) -> int {
    return span.end - span.start + 1;
}

func Main() -> int {
    let bounded: int..int = 2..4;
    let closed = 1...3;             // int..=int
    let from: int.. = 5..;
    let upTo: ..=int = ..=4;
    let everything: .. = ..;
    return Width(closed) - 3;
}

Bounds

The bounds of a range are numbers — integers or floating-point values — and both bounds of a two-sided range have one type. An unsuffixed literal takes the type of the other bound, so with n: uint, 0..n is a uint..uint and a loop over it counts in uint. A bound of any other type is an error:

error: range bounds must be numeric

Bounds of two different numeric types meet in a common type, the one a binary operator on them would use: with a: int32 and b: int64, a..b is an int64..int64, and with c: uint8, a..c is an int32..int32. When neither type holds every value of the other, the range is refused; convert one bound with as to the type the range should use:

error: range bounds 'int32' and 'uint32' have no common type

A range counts upwards. When both bounds are literals the compiler checks their order:

error: range start cannot be greater than its end

With bounds computed while the program runs nothing is checked, and a range whose start is past its end is empty: a for over it runs zero times.

Precedence

The range operators bind more loosely than every other operator except assignment — more loosely even than ?: and ??. So n + 1..n * 3 is (n + 1)..(n * 3) and flag ? 1 : 2..5 is (flag ? 1 : 2)..5, while a range inside a comparison needs parentheses: r == 0..5 parses as (r == 0)..5. Ranges do not chain — 1..2..3 is a syntax error. See Expressions.

Members

A range that has a start exposes it as .start, and one that has an end exposes it as .end; both have the bound type. Asking a range for a bound it does not have is an error:

error: range type '..int' has no member 'start'
  note: range members are 'start' and 'end' when that bound is present

The members need no import. A range held in a var binding can have its bounds assigned, as fields can.

In a for loop

for walks a range from its start upwards in steps of one, binding each value to the loop variable, whose type is the bound type:

for i in 1..=5 {
    PrintLine("{}", i);   // 1, 2, 3, 4, 5
}
  • a..b visits a up to b - 1, and a..=b visits a up to b. An inclusive range ending at its type's maximum visits that value last and stops, so for v in start..=255 over uint8 never wraps around.
  • a.. has no end, so the loop runs until it leaves with break or return.
  • A range with no start has no first value and cannot be walked:
    error: range type '..int' has no initial value and is not iterable
    

The loop variable is immutable; a loop that must control its own stepping is a while.

In slicing

Indexing an array, a slice or a pointer with a range produces a slice. Every form is allowed on an array or a slice: an omitted start means 0 and an omitted end means .length. A pointer has no length, so slicing one requires an end — p[..n], p[a..b] or p[..=n]; see Pointer slicing.

let values: int[6] = [10, 20, 30, 40, 50, 60];
let middle = values[2..4];    // 30, 40
let head = values[...2];      // 10, 20, 30
let span: int..=int = 1..=3;
let held = values[span];      // 20, 30, 40

In patterns

A range pattern matches every value from its low bound to its high bound. Both bounds are required, and both must be literals of the subject's type; the subject is a number or a character:

func Grade(score: int) -> char8[..] {
    return match score {
        0..50 => "fail",
        50...69 => "pass",
        70..=89 => "merit",
        90..=100 => "distinction",
        else => "invalid"
    };
}

A pattern with one bound missing, such as 90.. =>, is a syntax error. A character range such as 'a'..='z' takes bounds of the subject's width, so a char8 subject is matched with c8'a'..=c8'z'. See Patterns.

A type can also accept a range in its own indexer, such as func [](self: &Vect, span: int..int) -> int[..].

See also