`...` vs `..=` for inclusive ranges


me too! dislike …= syntax, like … and …


For reference, Mesa pg 15-16 shows that Mesa actually uses the non-symmetric mathematical range syntax [a .. b) with it’s 4 permutations: [], (), [), (].

I actually find that quote surprising because that syntax generally seems quite clear. That does beg the question, is the confusion because the syntax is too similar?

For example, surely @ker’s syntax using keywords would at least avoid this confusion. I’m not suggesting all 4 types should be supported or that that syntax should be used (others can decide). Just that it might be possible to avoid the confusion that article refers to while supporting all permutations.

It might be good to keep this unstable if you want to try a 4 permutation system, giving the option of backing it out if it’s confusing. Might be worth experimenting.


Doesn’t work in patterns, unless Ending becomes a lang item. Also, ugly.

match ch {
    'a' .. Inclusive('z') | 'A' .. Inclusive('Z') | '0' .. Inclusive('9') => { ... }


For reference, Collections already exports

Bound<T> = Inclusive(T) | Exclusive(T) | Unbounded


..= and ... are both clear in their own ways. ..= is clear in that it obviously includes the upper bound, but it’s unclear in that it’s not obviously a range at all (it looks a lot like an augmented assignment operator to me—perhaps a ..= b is equivalent to a = a..b :stuck_out_tongue: ). ... is clear in that it’s obviously a range (it looks very similar to ..), but it’s unclear in that it, well, looks very similar to ...

I personally prefer ... mostly because it looks a lot nicer, but also because it’s obviously related to .. and doesn’t look like an assignment of some sort. Although I am concerned that .. and ... would be hard to distinguish, I feel that there are only a few places where you really need to care about the distinction:

  • When writing code. I don’t think the particular range syntax would matter for this, except for the rare case of typos (which I consider unlikely).
  • When debugging code that you suspect has a bug caused by an off-by-one error. When doing this you’re likely to be looking very closely for the distinction between inclusive and exclusive ranges anyway, and so I don’t think the range syntax would make much of a difference here.

When reading code normally, I don’t think the distinction between inclusive and exclusive ranges is one that would be particularly important. Assuming the code is free of bugs, a range between characters is probably going to be inclusive, a range between 0 and something called len is probably going to be exclusive, etc…

That means that (in my opinion) the only place where the range syntax really matters (apart from aesthetics) is in learning the language. I can imagine it being frustrating to remember which syntax does what, but we do have a good mnemonic—‘more dots, more numbers’. Which is why I like ...—it just looks nicer, but in my opinion doesn’t have too many downsides.


Why must the sufix be symbolic?

Why not use something like …incl


I would actually prefer 0 upto / till len for exclusive 0 to len for inclusive (or something like that)


For reference, Scala uses to for exclusive, and until for inclusive ranges.


It’s a bit painful to choose here. I never liked the .. versus ... confusion. But I find ..= to be incredibly jarring. Because = is a big fat symbol, and always implies a comparison or assignment, it kind of stands out. Making it part of an operator that doesn’t have to do with either is distracting, and frankly having a three character operator at all is ugly. I would be most okay with ~ or the proposed two-character symbols that include it.


I think we have our solution: one syntax to rule them all. use begin..end the way it is used now and create “overloads” for Included, Excluded and Unbounded.

No ambiguitiy or confusion with the syntax, if anything different is wished for, make it explicit!


[quote=“iopq, post:15, topic:1539”]if you want to match on values then you really want to do something like

match i {
    0..10 =&gt; foo(),
    10..20 =&gt; bar(),
    20..30 =&gt; baz(),


To me this an example of confusing ranges, not clear ranges. It is not instantly clear to me that 10 matches the second statement and not the first. Yes, one understand that just by thinking a little bit, but the design of a language should try reduce this kind of little “distractions” that often lead to bugs.


But when you’re writing a range like this they are EASY to reason about:

  1. I am quite sure I didn’t miss anything

  2. The compiler made sure they don’t overlap

  3. Each of them has exactly ten elements

    match i { 5…15 => foo(), 15…25 => bar(), 25…30 => baz(), }

even this match statement has the same properties, while doing inclusive ranges would be error-prone Also, it should be fairly clear where 15 belongs, because if it was in the FIRST statement, then it wouldn’t satisfy the condition that it’s ten elements, so it MUST belong to the second


I think these are kinda nitpicky but relevant. This could be debated forever I’m sure and I do think exclusive syntax is useful.

I’m just pointing out there are things about exclusive syntax which can make it less immediately obvious than inclusive.

Actually, reading [0..4)is kinda like saying “solder”. Both are interpreted completely different than they look:

  • [0..4) looks like it should end with 4 but it doesn’t
  • solder looks like “solder” but sounds like sawter

The general rule is things probably should mean what they look like and this deviates. Deviating means the programmer must interpret more rather than less.

@iopq: I would argue the difference between these is:

// You have to compute in your head that the endpoints
// are 9 and 19 but visually the end (10) and beginning (10)
// look the same. The compiler checks exhaustively so it
// shouldn't care.
// So, easier for the programmer to see the alternates are exhaustive.
// More difficult to determine what the range actually means.
match i {
    0..10 => foo(),
    10..20 => bar(),
// You annotate the endpoints explicitly but the end
// and beginning look different. However, rust checks
// exhaustively so is less of a problem. It's also
// fairly easy to see if something just has 1 added to it
// So, easier for the programmer to interpret a specific alternate
// (without lots of practice). More difficult to check manually for
// exhaustiveness (rust does it for you anyway).
match i {
    1...10 => foo(),
    11...20 => bar(),


For integers, though, rustc won’t really check exhaustiveness in this case anyway because you need a _ => case to handle the integers that are out of (logical) range. Unless you’re willing to write 0 | 21...uint::MAX => instead.

  1. No the compiler does not make sure they don’t overlap. The following program works today:
fn main() {
    let x = 0u32;
    let y = match x {
        0 ... 10 => 1u32,
        10 ... 20 => 2u32,
        20 ... 30 => 3u32,
        _ => 4u32,
    println!("{}", y);

The compiler cannot enforce the non-overlapping check either because:

  1. The wildcard _ overlaps with everything.
  2. The compiler cannot check if an arm z if some_complicated_condition(z) => ... will overlap with others.


Wait what? How do they check if they are exhaustive then?


It’s not checked for integers, for integers you always need a catch-all branch.

If you have

let x: u8;
match x {
    0 => foobar(),
    1 => foobar(),
    255 => foobar(),

the compiler will still complain that you missed the _ (the catch-all) case.


I would favour less sugar:

// exclusive ranges
for i in 0..10 { ... }

// inclusive ranges
match x {
    Pat(0, 9) => ...
    _ => ...

and to keep ... as an illegal token (the usage above should give good justification for this).


There is no sugar. A special syntax is required to match between a number and a range.

In your example we need to change “Pat” to some keyword otherwise it will conflict with a struct named Pat.


For me as a relative newcomer ..= looks like some kind of assignment statement. Perhaps .=. or =..= is better?