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:
| Expression | Type | Contains | Members |
|---|---|---|---|
1..4 | int..int | 1, 2, 3 | .start, .end |
1..=4 | int..=int | 1, 2, 3, 4 | .start, .end |
1.. | int.. | 1 and every value after it | .start |
..4 | ..int | every value before 4 | .end |
..=4 | ..=int | every value up to 4, inclusive | .end |
.. | .. | everything | none |
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..bvisitsaup tob - 1, anda..=bvisitsaup tob. An inclusive range ending at its type's maximum visits that value last and stops, sofor v in start..=255overuint8never wraps around.a..has no end, so the loop runs until it leaves withbreakorreturn.- 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[..].