Ranges
The ends a range has are its type - Range, RangeFrom or RangeTo - so nothing is optional and nothing panics; what accepts every form takes the Bounds trait.
A range is an ordinary value, and which ends it has is its type: a..b is a Range, a.. a RangeFrom, ..b a
RangeTo. No field is optional in any of them, so for index in ..10 and (0..).length() are refused where they are
written instead of failing at run time. What an open end means is decided by whoever takes the range - a slice reads it
as "from the start" or "to the end of the value" - and what accepts every form takes the Bounds trait.
Example#
const bounded = 0..10
const inclusive = 0..=10
const endless = 3..
const upToTen = ..10
print bounded.contains(5)
print inclusive.contains(10)
print endless.contains(1000)
print upToTen.contains(9)
print bounded.length()
for value in bounded {
print value
}Syntax#
0..10 Range<Int>: from 0 up to but not including 10
0..=10 Range<Int>: from 0 up to and including 10
0.. RangeFrom<Int>: from 0, no end
..10 RangeTo<Int>: no start, up to but not including 10
..=10 RangeTo<Int>: no start, up to and including 10
range.start range.end the ends the type has, each a Value
range.inclusive a Bool on Range and RangeTo; an adjective, because it is dataRules#
One type per pair of ends.
Range<Value>hasstart,endandinclusive;RangeFrom<Value>hasstart;RangeTo<Value>hasendandinclusive. Nothing is anOption, so a range that has no start has nostartto read at all.Range<Int>isIterate<Int>andLength,RangeFrom<Int>isIterate<Int>and endless,RangeTo<Int>is neither. Aforover a range that has no start and alength()on a range that has no end are compile errors, each with the reason:Does not compilefor index in ..10 { print index } // error: `RangeTo<Int64>` is not `Iterate`, so `for` cannot walk itDoes not compileconst endless = 0.. print endless.length() // error: `RangeFrom<Int64>` has no member `length`Bounds<Value>is what every range is, and what accepts all three. It answerslowest()andhighest()as anOptioneach,includesHighest()as aBool, andcontainsis its default.Slice.slicetakes it, which is why all five spellings work in brackets:const items = [1, 2, 3, 4, 5] print items[1..3] print items[1..=3] print items[..2] print items[3..] const text = "hello" print text[(text.indexOf("e") ?? text.start())..]A range of something other than an integer is bounds and nothing more.
'a'..'z'says what is inside of it, because there is no "the next value aftera" in general - onlyIntranges iterate.inclusivestays a field and does not go into the type.0..10and0..=10are bothRange<Int>, andinclusiveis readable and comparable like any other field. Rust pulls it into the type and therefore has six range types; this has three, one per pair of ends, because that is the distinction a body actually has to make.The three types, like every built-in type, are declared in the prelude and not built into the grammar.
0..10isRange(start: 0, end: 10)written with an operator; the fields are ordinary fields.
What this is not#
An open end is not a hole in the type system. There is nothing to check at the point of use, because the type
already says which ends there are - and a body that needs a start takes a Range or a RangeFrom and lets the
signature refuse the rest.
A range pattern is not a range value. 4..=9 => "medium" inside a match is syntax: it compares, it builds
nothing, and none of the three types is involved.
Related#
- Strings -
text[from..to], a range passed to a slice. - Built-in types - the ranges next to every other type that needs no import.
- Integers -
Int, the typefor value in 0..list.length()counts through.