Inconsistent syntax for normal range and match range

When you’re using a range literal in a for i in 1..10 { … } statement, it uses two dots and the last element isn’t included, but in a match statement, it’s 1...10 and the last element is included.

Take the following code:

fn main() {
    let x = 42;

    for i in 0..42 {
        if i == x {
            println!("Foo");
        }
    }

    match x {
        0...42 => println!("Bar"),
        _ => unreachable!(),
    }
}

This will only print out "Bar", which is pretty confusing and inconsistent, mainly because they are not interchangeable as inclusive / exclusive variants of the same notation.

rust is basically going against what all other languages do with the … notation http://en.wikipedia.org/wiki/Ellipsis_(computer_programming)

but having two different notations for inclusive and exclusive is not inconsistent or unheard of. the main issue is that not both notations are available at both locations, but that should be post-1.0 compat i guess

1 Like

Oh yeah, I wasn’t talking about inclusion exclusion, just that they are two separate things, that appear in different places and are incredibly similar in syntax. I’ll go clarify that in the original post.

This is intentional, .. indicates an exclusive range, while ... indicates an inclusive one. It is consistent.

Rust used to have .. indicate either inclusive or exclusive ranges depending on contexts. That was inconsistent.

EDIT: And “match statements” were exactly the contexts where .. was used for inclusive ranges, so now we have ... there, but the ability to use .. as inclusive ranges there has not been added it seems.

Yes, that is consistent.

What is not consistent (and what I was ineptly trying to point out) is, that ... is not a range notation, since it doesn’t translate to the same type as .. does.

Equally, .. is not valid in the context of match.

What I’m proposing is, that both ... and .. work both in match and outside of it. For example, it’s currently impossible to write the following:

match x {
  0.0 => { … },
  0.0 ... 1.0 => { … },
  1.0 => { … },
};

because match doesn’t have an non-inclusive range syntax. Equally, it’s not possible to write:

for i in 1...10 { … };

because 1...10 is not a valid range literal.

EDIT: Reading this again I felt that the emphasis on some words might have come across a bit rude, which was not the intention :smile:

4 Likes

Sorry if I haven’t been clear, the “EDIT” in my previous post was intended to give some historical contexts on why .. and ... are only permitted at different places. I agree that this inconsistency should be eliminated.

EDIT: No offence taken, plainly stating facts can have this effect sometimes. And I don’t mean to be rude too. :smile:

1 Like

Maybe the best way is to make match accept a Range as a pattern, instead of having a separate syntax for it? That way, the following would be valid too:

match x {
  range(0, 10) => { … },
}

It would eliminate an entire syntax feature while keeping the code valid without changes, so it’s backwards compatible.

I don’t know why Rust chose to switch the meanings of .. and ... to the opposite of what people expect, why is that?

@iopq, RFC 198 is the RFC that introduced the ... notation and adjusted the semantics of the existing .. notation.

Particularly, this comment by @kballard explained why .. should be exclusive and ... should be inclusive.

I think Ruby got it wrong. My expectation in seeing interval operators is that a..b is the half-open interval [a,b) and a...b is the closed interval [a,b] . That said, this is an incredibly difficult thing to google for, so I'm finding it difficult to gather any supporting evidence for this notion. Although the one language that springs to mind already is Swift, which treats ... as the closed interval and used to treat .. as the open interval (which is now ..< ). For the record, my expectation around this operator pre-dates Swift, it's just an example of a recent language that came to the same decision.

And @pczarn said:

My expectation is that one more dot means one more element. Ruby got it backwards since these operators weren't introduced in tandem. Historically, the exclusive ... operator didn't exist before Ruby 1.4.

And then RFC 439 is the RFC that introduced the range notation that could be used in places other than match arms and indexing positions ("slicing").

@sindriava, basically, Rust used to use the range function for creating ranges, but it now prefers the range notation. This is somewhat the "reversal" of what you proposed, as this syntax feature is deemed desirable.

1 Like

@CloudiDust Thank you for the clarification!

My main point was however that .. is only valid outside of a match and ... is only valid inside of a match. Attached is a pen that should make my point clearer :smile: Try running it and look at the compilation errors.

http://is.gd/Sn47Zz

What we need to solve before 1.0 is out this: There is currently not an inclusive / exclusive range literal, only an exclusive one. There is currently not an inclusive / exclusive range pattern, only an inclusive one.

I propose two possible solutions:

  • We extend the current literal and pattern syntax to include inclusive and exclusive versions respectively
  • We unify range patterns with range literals internally

i don't think it needs to be solved before 1.0. Any code working now, will also work when inclusive range literals or inclusive range patterns exist.

I agree with @ker.

P.S. When I said Rust used to use the range function for creating ranges, I failed to make clear that range was not used in match arms or indexing positions then (these places always used the ../... notations), so it is not a direct reversal of your proposal.

EDIT: @ker, I believe you meant “exclusive range patterns” right?

I am strongly against the idea of having both .. and ... valid syntax at the same position, since a subtle off-by-one bug could be caused by a single innocent-looking character.

I would be OK if ... is changed into something like ..= (Similarly to Swift’s ..<) or upto or ..upto or whatever that makes the difference more prominent.

1 Like

The problem that bothers me in x..y notation is that symmetric operator .. is used to express asymmetric range. The “use three instead of two dots to include last element” seems totally arbitrary. This is really confusing (that’s the reason this topic appeared), especially when there exist languages (such as Haskell) in which x..y includes y.

My proposition is to change .. token into ..,, so that you would write:

for x in 0..,n {}

if you want to exclude last element. The idea is for it to resemble ... operator, with a comma superimposed, so you can think of it as "range of values starting at 0, and then there’s n".

Why I prefer .., to other solutions proposed in this topic (@theme)?

  • ..= for inclusive range: assymetric operator used for symmetric usecase; and I believe it would create ambiguity in grammar (could be parsed as x.. = y;
  • upto or ..upto for exclusive range: creates new keyword; for me as non-native english speaker it’s not obvious whether it excludes last element; and it’s just longer;
  • ..< for exclusive range (Swift’s syntax): it’s actually a good idea, but it somehow looks weird to me.

Before I started writing this post I liked .., because it is visually a superset of ... (which, what I’ve found, is not the case in monospace font on this forum, but here it works: “…,”). Actually, I wanted to propose ., and revert .. to it’s old semantics, but I realised that (0.,n) would be ambigous.

(note that changes I’m talking about are completely ortogonal to unification of expression and pattern syntax for ranges)

No matter what syntax we will end up with, I would be really sad if current (.. and ...) syntax will stay for 1.0 and I just want assymetric range to be represented by an assymetric operator.

1 Like

I dislike .., because it is too similar to ... (which is, ironically, the reason that @krdln liked it). I’ve just realized, though, that upto is still ambigous.

I don’t think that ..= would cause a grammar ambiguity, since x.. is not a valid pattern (correct me if I’m wrong.

BTW the .., syntax does cause a grammar ambiguity in foo(x..,y) which might be parsed as a 2-argument call foo( (x..), (y) ) and in (x..,y) which might be parsed as a tuple ( (x..) , (y) )

I considered that .., may be too similar to ... for some people and that's why I think that using ..; or other variants would be perfectly good ideas too.

I don't think that ..= would cause a grammar ambiguity, since x.. is not a valid pattern (correct me if I'm wrong.

You're right that it's not a valid pattern, but assignement expression is 'expr = expr', and x.. is a valid expression today.

BTW the .., syntax does cause a grammar ambiguity in foo(x..,y)

Thay would be ambigous if we had left .. in the language. But I was proposing only valid range operators to be ... and .., so that's no problem.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.