We are planning to to change the syntax for inclusive ranges and patterns to ..=
. The ...
syntax in patterns is stable and will remain (silently) deprecated for the time being; rustfmt can rewrite ...
to ..=
. This comes after much discussion. See this comment for justification.
No more syntax discussion should be had in this thread. Any different proposals of exclusive range syntax should take place on the user's forum or internals forum, after you have read all existing comments and their rationale here. Notably, breaking backwards compatibility is a non-starter.
..=
as synonym for ...
in patterns and to accept ..=
in expressions #44709RangeInclusive
RangeInclusive
have been left out from this round of stabilization. Please track #49022 on this feature.Maybe a..b|
or a..b]
I think this will open up the way for the new kind of unpredictable errors in future because of a simple typo (.. vs ...). Better if it was .... (4 periods). This way it is less error-prone to the human factor, imo.
I think https://github.com/rust-lang/rfcs/pull/1592 and https://github.com/rust-lang/rfcs/pull/1582 combined make a case for using the ..=
syntax instead. Unless someone can think of better syntax than (head..., tail)
for expanding a tuple at the front of a larger tuple.
I've found this issue because I had _off-by-one-dot_ error in my code when I've meant to use exclusive range.
👎 for the ...
syntax. I think having an easy-to-mistype syntax that causes off-by-one errors would be a footgun.
The functionality is useful though, so I'd be happy to have it with a different syntax, e.g. ..=
Is the syntax for this an open question? Since ...
is already in match statements I assumed that ship had sailed.
I'd personally prefer the ...
inclusive range, however since there is already the ..
exclusive version, I I see the potential for problems. After looking at #23635 though I'd rather deprecate ..
and only allow ...
though.
I use inclusive ranges a lot for equivalent of C-like for loops for i in 0..foo.len()
where it fits perfectly, so I'd prefer that one to stay (I need this, because Rust's iterators are "1-dimensional" and often too awkward to use with 2D arrays or non-linear iteration).
The problem with overflow for inclusive ranges looks silly, but in practice I never ran into this problem, because Rust is annoying to use with any type other than usize
. If I didn't cast when creating the range for i in 0..(len as usize)
, then I'd have to use i as usize
half a dozen times inside the loop anyway.
Since this syntax is still feature-gated I hope the ship hasn't sailed.
Considering that swift uses ...
for inclusive and ..<
for exclusive ranges, using ..=
for inclusive seems pretty reasonable.
I don't have any useful insights, but I'd like for inclusive ranges to exit "experimental" status. As I was going through Rust By Example, I found one snippet that could benefit from this:
fn fizzbuzz_to(n: u32) {
for n in 1..n + 1 {
fizzbuzz(n);
}
}
Up ? 😄
I want to write an RFC for a ..= b
syntax and generalized ranges. I've started a discuss thread to talk about how such ranges would be represented in the standard library.
IMHO ..= looks weird. Swift's approach of ... and ..< looks better to me, because I prefer the ellipsis over two dots - ellipsis stands for omission and we are omitting the numbers between the start and the end of the range.
I still think ... and .. was good enough. You have 1 character difference, so the mistake is harder to make than +/- or x/y or whatever.
Since I misunderstood this myself earlier (and so deleted my comment):
Per Rust's RFC process, this proposal has already been reviewed, discussed, and approved in RFC pull request 1192. The present issue tracks the implementation of what was previously decided there. The discussion covered many of the points people are raising here: alternative syntax (including no new syntax), the contrast with Ruby's similar operators, etc.
If you feel strongly that the feature should be different, I think you need to take it through the same RFC process, because that's how changes to the language get made. But this issue isn't the place for that.
@jimblandy maybe we should have @nikomatsakis edit that polite reminder and guidance into the first comment in Really Big Print. 😇
@shepmaster That would probably be a good thing to add to a template used to file _all_ tracking issues.
Nominating for discussion/possible FCP
We discussed this in the @rust-lang/lang meeting. There was a general sense of unhappiness with this feature -- we considered moving to deprecate, but decided to hold off on that for the time being. There are two major objections to ...
as it stands:
..
and ...
;To that end, we were wondering if someone would be willing to drive forward an RFC that enabled a more general syntax like that let people specify precisely whether the lower- and upper-bounds would be inclusive or exclusive. I think @aturon would be happy to work with someone on such an RFC.
I know I stalled for a while, but I recently opened a discuss thread about how to represent those fully-capable ranges in libstd (linked above), but nobody commented :(
With some input on the above, I'd be happy to help with a new RFC.
I recently opened a discuss thread about how to represent those fully-capable ranges in libstd (linked above), but nobody commented :(
That somewhat reflects their usefulness.
While having arbitrary inclusive-exclusive ranges sounds like a nice idea, I'm pretty sure everything except for ..
and to much less degree ...
will never ever be used.
@durka
I know I stalled for a while, but I opened discuss thread about how to represent those fully-capable ranges in libstd (linked above), but nobody commented :(
This looks like roughly the approach we had in mind, yeah.
@petrochenkov
While having arbitrary inclusive-exclusive ranges sounds like a nice idea, I'm pretty sure everything except for .. and to much less degree ... will never ever be used.
To be honest, I agree, but I still think it may be worth pursuing a more general syntax. In particular, I think that ...
is suboptimal, but if we move to ..=
or something more explicit, it probably doesn't really hurt to do something a bit more general, even if it's rarely used. That is, if we make it systematic, it doesn't seem much harder to learn, and it's surely the case that there would be no confusion as to whether ..
or ...
means "more numbers".
This looks like roughly the approach we had in mind, yeah.
Which one? I suggested several alternatives in my post.
I often want to iterate over inclusive ranges and really liked typing 0...x
instead of 0..(x + 1)
. I understand that this may introduce off-by-one errors, but what alternatives do we have?
Assuming Rust's syntax could be freely changed without side-effects, I see only a few obvious patterns:
Taking it as it is
1..4 // 1, 2, 3
1...4 // 1, 2, 3, 4
which is an okay solution IMHO and also used in pattern-matching.
Borrow from mathematics
[1, 4] // 1, 2, 3, 4
[1, 4[ // 1, 2, 3
]1, 4] // 2, 3, 4
]1, 4[ // 2, 3
which is the most complete syntax, but breaks the same amount of opening and closing braces rule and is visually harder to parse.
Borrow from Python
1:1:=5 // 1, 2, 3, 4, 5
1:1:<5 // 1, 2, 3, 4
which is a known pattern and could also incorporate a stepsize. I do not know how the RFC process works, but I think a stepsize should also be considered when talking about ranges.
When the stepsize is not part of this discussion (or a future one), ..=
and ..<
seem very reasonable!
Update: I think ..=
and ..<
would be a really good solution. Keeping the stepsize as an adapter makes a lot more sense.
Should this discussion incorporate descending ranges? The current solution of inverting the range is quite confusing (but perhaps wouldn't if we had inclusive ranges).
e.g. Can you read and understand this without squinting?
for i in (1..l.len()).rev() { ... }
EDIT: and with GitHub's font it's even more confusing as l
looks like 1
I think a negative stepsize would suffice.
We could go with the Matlab syntax, a:b
and a:step:b
.
On Nov 4, 2016 00:50, "Ott" [email protected] wrote:
I think a negative stepsize would suffice.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-258344460,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3n3kwnGH6POQb4dGwCwJ8yOGqPSBQks5q6rmDgaJpZM4F4LbW
.
@durka So what would be the equivalent of the current ..
, where there are no a
or b
? Just :
?
Something I didn't see noted in the RFC: Were different names considered for the fields?
If there are different types, it might be valuable to have different field names when they have different meanings. For example "range.start" is "the lower inclusive bound" on all the types. But "range.end" is sometimes inclusive and sometimes exclusive. A distinction between "end" and (strawman) "last" makes that clearer.
(This is, of course, irrelevant if the different types are abandoned in favour of Range
I think inclusive range should still implement std::collections::range::RangeArgument.
pub trait RangeArgument<T> {
fn start(&self) -> Option<&T> { ... }
fn end(&self) -> Option<&T> { ... }
}
Then
impl RangeArgument<T> for RangeToInclusive<T> {
fn end(&self) {
Some(self.end+1)
}
}
So that fn foo<T, R: RangeArgument<T>>(arg: R)
can take either inclusive or half-open ranges.
@durka The problem with the a:b syntax is that it's ambiguous with type ascription. Are you asking for a variable b
, or a type b
? Obviously as good Rust programmers we capitalize our types, but the compiler can't infer that.
.. operator with math definitions (based on @duesee comment):
[1..4] // 1, 2, 3, 4
[1..4[ // 1, 2, 3
]1..4] // 2, 3, 4
]1..4[ // 2, 3
Using (based on @0tt example):
fn fizzbuzz_to(n: u32) {
for n in [1..n + 1] {
fizzbuzz(n);
}
}
In this case we use ..
just as a hint, but the boundaries are given by [
and ]
@adelarsq: In Rust we don't need ranges that don't include the start. But the bracket syntax only makes sense when those are allowed. Plus, bracket syntax is already used.
I think ..=
is the most reasonable choice for inclusive ranges.
Easy to visually distinguish from ..
, which ...
is not. The chance of typing ...
when meaning ..
or vice versa and not noticing is higher than with ..=
.
(It's comparable to accidentally typing if(a = b)
instead of if(a == b)
in C, both in visual noticeability and bug potential).
And ..=
is easy to memorize because it's symbolic for it's meaning (i ..= j
means the range i .. j
and where i = j
).
It seems that the major complaint is that ...
is too similar to ..
. I noticed that when other suggestions are made that involve more characters no one complains about the number of characters, only things like bracket matching and use in certain places.
So, what about ....
for inclusive ranges? It should still look like a range, and as I said no one seems to mind a solution that takes an extra character. It's debatable whether it is more or less wonky than ..=
.
for i in 0....10 { println!("just a thought"); }
@adelarsq, the ....
operator would replace the ...
operator, so if you tried to do ..
and added an extra .
, it would be a compile time error, if you tried to do ....
and forgot a dot, that would be an error as well.
Since in pattern matching, ...
is used to match inclusive range, so it make sense to also use ...
for inclusive range expression.
For range with step, I prefer the Haskell syntax, e.g. 1,3..9
=> [1,3,5,7]
For ranges with step/reverse, etc. I'd prefer a good old-fashioned function (some sort of Range
constructor) instead of a complex literal.
I'd be OK with quad-dot ....
(although ..=
is IMHO fine too). It could be allowed in pattern matching too for consistency (and eventually ...
in patterns could be deprecated).
For me ..=
as a visual signification that ....
don't have.
I just noticed something:
In mathematics you could write ∀ i: 0 ≤ i < 10
which would translate into
for i in 0 ..< 10 { }
This is consistent.
However, the statement ∀ i: 0 ≤ i ≤ 10
would translate into
for i in 0 ..= 10 { }
Here, ≤ translates to =, which feels inconsistent.
This might be bikeshedding, but
for x in 0 ..<= 10 { }
feels more correct. This may be due to my C background where
for (unsigned int i = 0; i <= 10; ++i) { }
translates into "as long as i
is smaller or equal to 10
do something". In C I prefer not to use ==
in loop conditions, because of the possibility to jump over the value and end up in an infinite loop. This can not happen in Rust, but translating for i in 1 ..= 10
to C might suggest exactly that.
Taking it further,
for i in 0 <..<= 10 { }
would be self-explanatory.
Edit: here was a bad reverse example meant to show how using only =
might be confusing. Deleted, since it was more confusing than constructive.
@duesee How is >=..>
self-explanatory? Shouldn't it be <=..>
(<= 10 and > 0) ?
IMO it's not self-explanatory, it's cryptic and too long as an operator.
(No operator should be longer than 3 chars IMO).
Btw, we already have a way to express reverse iteration direction: .rev()
We only need operators for inclusive ranges and maybe .step_by()
.
For the step the syntax could be a .. b | s
(and for inclusive ranges: a ..= b | s
).
@norru: Your example for i in (1..l.len()).rev() { ... }
is very unrealistic, as most of the time you would use slices (for x in arr[1..].rev() { ... }
), but even if you are forced to use the index-style of iteration (basically only when you modify array elements that are not at i
), I don't find it hard to read at all. (But I usually leave spaces around ..
when the args are not literal numbers: (1 .. arr.len()).rev()
)
>=..>
more species of fish in Rust operators!
But I do like ..<=
a lot (edit: uhh, except in match
where it looks like <= 0 =>
:()
..
, +=
connotation of ..=
, ..<
. It's possible for both languages to converge!@pornel: >=..> more species of fish in Rust operators!
Hehe :-)
@Boscop: it is self-explanatory if you read it like a definition in math: 10 >= i > 2
. For the rest of your answer: I absolutely agree. The second part was meant as a motivation to rethink ..=
vs. ..<=
as not to introduce blockers for future extensions. I will edit my answer accordingly.
can also get a flag to warn or error on ..
notation
I don't want ..
to appear anywhere in my codebase
I'd like to put another vote for ..=
The downside to ..<
and ..<=
is that they are already valid code (a right-open exclusive range being compared to another value).
@thepowersgang
The downside to ..< and ..<= is that they are already valid code (a right-open exclusive range being compared to another value).
while true, it seems pretty unlikely for this to be a problem in practice, no?
I vastly prefer ...
to the other suggestions here.. but if I had to pick an alternate syntax, it would be ..=
@nikomatsakis
I'm against the ..<
and ..<=
options purely on the grounds that they disproportionately complicate the process of acquiring an intuitive model of the language's grammar.
On a more personal note, I find them at least as ugly and inelegant as the turbofish.
I'd go with ..
for right-exclusive and ..=
for inclusive as the best balance between visual elegance, grammar rules that are easy to intuitively grasp, and making the two difficult to confuse.
Is ...
really that indistinctive from ..
? Why do I like it so much?
@tshepang
I like ...
because it's aesthetically appealing... but I argue against it because I worry that it has the potential to cause hard-to-notice bugs similar to writing if (x = 2) {
rather than if (x == 2) {
in C/C++. (And it's well established what a hazard that turned out to be.)
@ssokolow
IMO that's better resolved by linting or option flags. I don't see much use for mixing notations in a single file/project.
Also prefer ....
over the other options, having double the width/spacing should make it obvious enough especially considering they will be flanked on both sides by symbols which will make that negative space more pronounced than in isolation.
@ssokolow
I'd go with .. for right-exclusive and ..= for inclusive as the best balance between visual elegance, grammar rules that are easy to intuitively grasp, and making the two difficult to confuse.
It works for me. The main sticking point is whether we also want a syntax for other cases (e.g., <..<=
). I personally feel that ..
and ..=
covers like 99.5% of cases. I think the main use case is APIs like drain
, and we have currently adopted a distinct approach there.
cc @rust-lang/libs
We can leave the uncommon cases to directly constructing the ranges using
struct literals.
On Wed, Feb 22, 2017 at 4:50 PM, Niko Matsakis notifications@github.com
wrote:
@ssokolow https://github.com/ssokolow
I'd go with .. for right-exclusive and ..= for inclusive as the best
balance between visual elegance, grammar rules that are easy to intuitively
grasp, and making the two difficult to confuse.It works for me. The main sticking point is whether we also want a
syntax for other cases (e.g., <..<=). I personally feel that .. and ..=
covers like 99.5% of cases. I think the main use case is APIs like drain,
and we have currently adopted a distinct approach there
https://doc.rust-lang.org/std/collections/struct.BTreeMap.html#method.range
.cc @rust-lang/libs https://github.com/orgs/rust-lang/teams/libs
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-281815665,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3n-aKwYKFq4VI9RizL0GkzXXSjTPwks5rfK2ngaJpZM4F4LbW
.
I take it ..=
will be made available in patterns too? What'll become of the existing ...
syntax there?
It can be deprecated. But its existence is a pretty strong precedent for
using it, at least as a shorthand.
On Wed, Feb 22, 2017 at 6:02 PM, andrewtj notifications@github.com wrote:
I take it ..= will be made available in patterns too? What'll become of
the existing ... syntax there?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-281834007,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3nw_DaOMNxuIKw6ZFfXJW95QyijY4ks5rfL6DgaJpZM4F4LbW
.
@durka @andrewtj I would presume that we would deprecate it.
..=
can be used as an operator in the same sense of +=
.
I suggest use tide ~
, e.g. 0~9
, 'A'~'Z'
.
What operation would ..=
correspond to? -
is obviously unviable because
1-9 == -8
.
On Wed, Feb 22, 2017 at 8:04 PM, Junfeng Liu notifications@github.com
wrote:
..= can be used as an operator in the same sense of +=.
I suggest use ~, e.g. 0~9, 'A'~'Z'.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-281856846,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3n7N2dM4DAzc_uc5JdHit4IJgyvYGks5rfNsPgaJpZM4F4LbW
.
@durka ..=
just look like assignment operators, the use case must be rare. ~
is tide not sub. Use ...
is also ok.
impl Fill for Range<String> {
fn fill() -> String { ... }
}
impl FillAsign for RangeAssign<String> {
fn fill(&mut self) { ... }
}
(String::new("abc") .. String::new("f")).fill() // "abcdef"
(String::new("abc") ..= String::new("f")).fill() // ()
Oh sorry, it looks the same in my font. I was asking, if ..= is a compound
assignment operator like +=, what operation does it perform (corresponding
to +)?
On Wed, Feb 22, 2017 at 8:29 PM, Junfeng Liu notifications@github.com
wrote:
@durka https://github.com/durka ..= just look like assignment
operators, the use case must be rare. ~ is tide not sub.impl Fill for Range
{
fn fill() -> String { ... }
}
impl FillAsign for RangeAssign{
fn fill(&mut self) { ... }
}
(String::new("abc") .. String::new("f")).fill() // "abcdef"
(String::new("abc") ..= String::new("f")).fill() // ()—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-281861478,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3n39uqbADB1rNtBh3PBTRY2XBXOUNks5rfOD-gaJpZM4F4LbW
.
@durka @nikomatsakis okay, well, I hope this is resolved soon. The current situation is awkward and changing syntax will be too.
@pornel Deprecating ...
in match
would break compatibility, so it isn't a good solution. I still find ...
the most adequate one (however it's an exact inverse of Ruby, but no similar problem is discussed by Rubyists).
@hyst329 but ...
can be more susceptible to errors, since missing or adding one .
will give a new meaning
Deprecation doesn't break things. The old syntax will have to be supported forever for existing programs, but users can be steered towards the new syntax via documentation and rustfmt.
but users can be steered towards the new syntax via documentation and rustfmt.
I strongly recommend against using rustfmt for that. It'd exacerbate the footgun-esque qualities of ...
when used in "automatically apply changes" mode because it could silently translate typos into things that look much more purposeful. (Thus making them easier to overlook on later examination.)
A compiler warning in the vein of "please check what you meant and use either ..
or ..=
" which can't be auto-fixed would be much more in line with existing efforts to minimize the chance for human error.
Please note I was talking in context of the existing ...
in patterns, where no footgun exists. Currently Rust is in a temporary footgun-free situation where patterns only allow ...
and expressions only allow ..
, so both are safe from typos.
Of course I would not recommend converting ...
in expressions to ..=
, as that would of course only make the typo permanent.
Ahh. I'd forgotten about that. Thanks for reminding me.
@adelarsq Actually, common errors like 'a'..'z'
or 128u8..255u8
can be easily integrated into cargo-clippy
as warnings, for example. And also, the width of ..
and ...
is clearly different (when using monospaced font, of course; not using it is usually a bad idea for writing source code in general - not only Rust).
We could just make the syntax for it be …
(unicode horizontal ellipses) instead of ...
That way at least nobody would type it by accident.
Edit: oh no people were treating this suggestion seriously :cry: Sorry for the noise.
I don't know, last I checked Google Docs automatically converts ... to ellipsis. Major player in the text editing industry.
Also, not everyone has a Compose key for conveniently typing Compose . . when they do want …
On Windows I can type ellipses using Alt + 0133
with the numpad! …
And similar codepoint-based mechanisms exist at various layers of the stack on X11-based desktops (I remember GTK+ and the X11 input stack having their own independent solutions) but it's a major hassle to remember codepoints by number.
Compose is a ṽèŕÿ intuitive solution.
The only Windows Alt sequence I remember is Alt + 219
from all of the DOS Batch file menus I made as a kid.
The current syntax exists for a long time already, let's just stabilize it already and stop this endless bikeshedding.
Keeping the current syntax is the worst we could do as it has many real disadvantages. This isn't just bikeshedding -- it should be done right. Skipping over the whole thread, the ..=
syntax got the most acceptance so far...
I do feel a bit like it's beating a dead horse at this point, but when you think about it... The difference between ..
and ...
is literally one copy of the smallest possible character you can type or read, mixed in with a group of identical characters, and has the potential to create an extremely common and often irritatingly-hard-to-find error class (off-by-one errors) in cases that are otherwise completely indistinguishable both to man and machine.
Or we could do literally any other syntax in the world.
On one hand I appreciate that concern.. on the other hand, the current ...
pattern matching syntax is already inclusive, making it perfectly consistent with syntax already used elsewhere in Rust.
And I don't personally have any issues recognizing the difference, although I recognize that people with a vision issues, or with a 4k monitor and 10pt font, might.
But it seems like the consensus is on ..=
and I guess I'm fine with that too.
FWIW, I've had that situation where I accidentally typed ...
when I meant ..
and then didn't notice for a while, but after a while I wondered why my program behaved strangely.
So yes, there should be a more visible difference, and ..=
makes the most sense.
Nominating for @rust-lang/lang discussion. I think we should just adopt ..=
and call it a day. Do we need an amended RFC? Just do it? Does this involve deprecating the existing ...
syntax in patterns? (I assume so.)
I'm strongly in favor of not using ...
for ranges. I don't know if it has been mentioned in this thread or the RFC thread already, but in addition to ...
being unreasonably similar-looking to ..
, we may also want to use ...
in the future for variadic generics, which are much more important than inclusive ranges IMO.
..=
seems clear, and it's relative ugliness is justified by being less common than ..
.
Does this involve deprecating the existing ... syntax in patterns? (I assume so.)
This seems like a good idea. Warn on ...
and support both ..
and ..=
in patterns if we can.
I'm generally wary of introducing warnings that would affect so many people without also having an automated tool for updating the syntax, but ...
to ..=
is a particularly simple change. We could also deprecate but postpone making it a warning until we have such a tool. What does everyone else think?
I am strongly in favor of ...
because it is more "usual" syntax; I don't know of any language that uses ..=
, but many that use ..
and ...
.
If sentiment against ..=
is strong, I would prefer to go with no inclusive range syntax (instead opting for an (a..b).inclusive()
method, which seems concise enough to me) to avoid the visual problem and again free up ...
for variadic generics.
EDIT: One good argument against (a..b).inclusive()
is that we'll still have ...
in match
and no new syntax to replace it with, unfortunately. :confused:
@steveklabnik Apologies if this is already mentioned somewhere in this (very long) thread, but: what other languages use both ..
and ...
for exclusive and inclusive ranges, respectively?
Rust has a history of adopting useful features and syntax from other languages, but selectively, and rejecting or adapting things where appropriate.
@joshtriplett Ruby uses both, but with opposite meanings (..
is inclusive, ...
is exclusive). I don't believe it was a good idea in Ruby, and it seems even more potentially confusing for us to have both, but backwards.
@joshtriplett the one I'm most familiar with is Ruby; I thought it got it from Perl but I'm not entirely sure. I'm more okay with the semantics being backwards from Ruby than I am with ..=
.
Honestly, I would prefer a stable inclusive range syntax over anything else, and recognize that my intense dislike of ..=
is a personal thing, that reasonable people may disagree on.
@steveklabnik Swift uses ..<
, I think, right? Which seems similar to ..=
but worse, in that it optimizes for (imo) the wrong case. =)
Swift uses ..< for exclusive and ... for inclusive (their terms are
"half-open" and "closed").
I still like ..< (with .. as shorthand) and ..=.
But another open question (maybe not within the purview of this tracking issue) is whether we "just" adopt a syntax for closed ranges, or also syntaxes (syntaxen?) for ranges with the first point open, i.e. strawman >..> and >..=.
On Thu, Mar 16, 2017 at 2:19 PM, Niko Matsakis notifications@github.com
wrote:
@steveklabnik https://github.com/steveklabnik Swift uses ..<, I think,
right? Which seems similar to ..= but worse, in that it optimizes for
(imo) the wrong case. =)—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-287147321,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3n0Wop5fJh9HVo7pSqo0riHm96Gm4ks5rmX1BgaJpZM4F4LbW
.
I wouldn't mind having an inclusive
function in the prelude:
for x in inclusive(1..10) {
}
I've never felt symmetry with match is a compelling argument. I think we decided to add exclusive ..
patterns, but I dislike those also - a value in the pattern is now not matched by the pattern! Matching a range is fundamentally different from iterating through a range, there's no reason they have to be symmetric. I'd have a mild preference to not allow ..
in patterns if its still an option, but I think it isn't?
I feel the downsides with ..=
and ...
are both significant - ..=
is quite odd, but ...
increases the likelyhood of off by 1 errors.
It's better to have an odd looking operator than such a never-ending potential for bugs (every language introduces operators that others don't have, e.g. ..<
in Swift, all the ops in F# and Scala). People will have to read the Rust book anyway.
Considering that a lot of people are coming to Rust from Ruby, we shouldn't have it backwards compared to Ruby. (In addition to the argument that ...
increases the likelyhood of off by 1 errors.)
How can you be accepting of ..<
in Swift but not accepting of ..=
in Rust? ..=
is not that odd.
Extending range syntax in patterns is still an open question as well AFAIK.
For one thing, we can't fully do it, because Enum::Variant(..)
is already
valid and changing it to mean Enum::Variant(RangeFull)
would be breaking.
On Thu, Mar 16, 2017 at 3:07 PM, Boscop notifications@github.com wrote:
It's better to have an odd looking operator (every language introduces
operators that others don't have, e.g. ..< in Swift, all the ops in F#
and Scala). People will have to read the Rust book anyway.
Considering that a lot of people are coming to Rust from Ruby, we
shouldn't have it backwards compared to Ruby. (In addition to the argument
that ... increases the likelyhood of off by 1 errors.)
How can you be accepting of ..< in Swift but not accepting of ..= in Rust?—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-287160485,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3nz9ZNR2utl7LPTVvQPQ_Ma8sDBGuks5rmYhmgaJpZM4F4LbW
.
So. I want to be able to type stuff like slice.get(10...30)
and eat my cake too.
I've convinced myself that I strongly prefer inclusive(0..10)
to any of the other syntaxes:
slice.get(10...30)
, I don't want to have to pay close attention to whether that's slice.get(10...30)
or slice.get(10..30)
. I think the closeness of the syntax is a real problem...=
is unaesthetic and probably unintuitive.(0..10).inclusive()
is the same syntax but less convenient. Technically, inclusive
prelude would probably support both syntaxes.I think it would look like this:
trait IntoInclusive {
type Inclusive;
fn inclusive(self) -> Self::Inclusive;
}
fn inclusive<T: IntoInclusive>(range: T) -> T::IntoInclusive {
range.inclusive()
}
I think that everyone agrees ..= is unaesthetic and probably unintuitive.
I think saying that is a bit of an over-generalization. I find ..=
to be more aesthetic and intuitive than a method like inclusive()
would be.
(Also, we don't know how much of the dislike for ..=
could be subconscious (or conscious) efforts to find problems with things which aren't ...
.)
.inclusive()
is too long for something that occurs so frequently (on that note, I also think there should be a shorter form for .enumerate()
).
But even if we had .inclusive()
, there should be only one function, not two.
Additionally, having no inclusive range syntax would not allow them to be used in match
.
But maybe we need a way to specify extractors in a general way, like in Scala? So that any type could be used in a match, so that match will implicitly call its unapply
method.
(Also, we don't know how much of the dislike for ..= could be subconscious (or conscious) efforts to find problems with things which aren't ....)
None from me. This is sort of an accusation of bad faith.
Additionally, having no inclusive range syntax would not allow them to be used in match.
Inclusive range patterns are already supported in match
. In fact they're the only sort of range we support as patterns today.
@withoutboats yeah but I said "having no inclusive range syntax would not allow them to be used in match". Right now there is a syntax but my argument was against .inclusive()
because it couldn't be used in match unless we had extractors like Scala. and the whole point of this issue is that inclusive range syntax shouldn't be a special case that only works in match and to determine what the syntax should be instead of the current one.
Inclusive: for i in 1..10! { }
As someone that work more that 15 years with Java/C/C++ I find that ..
vs ...
is pretty confusing and may cause bugs just by typing mistakes. Also the relation with Ruby turns this even more confusing. That is why ..=
or any other alternative is better.
throwing my 'hat' in the ring:
for i in 1..10^
The hat/caret symbolising that the right value goes all the way _up_.
@leebenson Personally I think it would be better if it was in the middle, not tacked on the outside.
for i in 1..^10
@retep998 I considered that after I hit send... that's probably nicer actually.
@retep998 I like your suggestion of ..^
much better than ..=
; it seems much more evocative of its meaning, and it avoids looking like a strange variant of an augmented assignment operator (like +=
and *=
). That said, either one would be far better than ...
.
I can't get behind ..^
.
On a personal level, I find it very ugly (partly because the ..
is so vertically disjoint from the ^
in many fonts).
As a UI/UX guy, I think it's an example of being too clever for your own good.
If I come in from another language and see ..^
, I may not even recognize it as a range (for all I know, 5..^15
could be some weird partial factorial shorthand for 15! - 4!
) because humans think in contexts and the only association that character has in a mainstream programming context is exponentiation.
(If we didn't think in contexts, ...
could mean something like "callback gets inserted here" or "emit event" by analogy to its "content omitted"-like meaning in dialogue in prose writing, just to show the most immediate example. Giving it a "goes all the way up" meaning via its resemblance to an arrow is like doing magic tricks via sleight of hand, as far as how it interacts with a newcomer's expectations.)
By contrast, =
has precedent for referring to both the action of assignment and the concept of equality and there's no "weird exponentiation syntax"-like misconception for ..=
because the closest you can get to "assignment + equality" that isn't already handled by the assignment operator is "something to do with a sequence of numbers, ending on the right-hand one"... which is a vague but appropriate definition of what range syntax does.
I also worry that, even if I did recognize that ..^
was range syntax from its use in context, I'd have no intuitive idea whether it was inclusive or exclusive at first, because there's no meaning for the ^
in the relevant context, except for the possibility that 2..^8
is shorthand for an open-ended range starting at two and stepping by taking the previous step to the power of 8. (ie. 2..Inf^8
as opposed to 2..+8
being shorthand for "iterate from 2 to infinity in steps of 8".)
That problem is also avoided by ..=
because people are used to thinking in terms of <
(exclusive) vs. <=
(inclusive) when writing while
loops and C-style for
loops.
@ssokolow legit analysis.
Personally, I can get behind either ..=
or ..^
. The symbology of either makes sense to me - i.e. 'up to and equal to' or simply 'up to', respectively. They both mean the same thing, to my mind.
It's going to be hard to come up with something that satisfies everyone, because we all bring history from other langs and with it, tainted symbology/bias. I initially added the ^
sign _after_ the number, for example, because inserting before had the feeling of representing a 'step' or exponent, whereas a suffix left the range untainted, and somehow purer feeling. But that's just me.
In any case, I would prefer a shorthand notation in some form vs a function call that implicitly +1s the right-hand val. I agree with earlier comments that this is too common a syntax to delegate to something that feels like a function call. I even wouldn't mind ...
, but granted, it's probably the =
/ ==
bug in new clothing and bound to shoot someone in the foot...
lots of people seem to actively dislike ..= nobody said anything negative
about .... it was just quietly ignored (except for a few thumbs up) I
thought I would bring it up again and have people consider it and give a
reason for not using it if they are against it.
I hate to even mention it because it's just more noise, but what about .:
(dot colon) for inclusive? It's 3 dots but 2 characters, and I think the
shape makes it distinct from ..
On Sat, Mar 18, 2017 at 11:50 AM, Lee Benson notifications@github.com
wrote:
@ssokolow https://github.com/ssokolow legit analysis.
Personally, I can get behind either ..= or ..^. The symbology of either
makes sense to me - i.e. 'up to and equal to' or simply 'up to',
respectively. They both mean the same thing, to my mind.It's going to be hard to come up with something that satisfies everyone,
because we all bring history from other langs and with it, tainted
symbology/bias. I initially added the ^ sign after the number, for
example, because inserting before had the feeling of representing a 'step'
or exponent, whereas a suffix left the range untainted, and somehow purer
feeling. But that's just me.In any case, I would prefer a shorthand notation in some form vs a
function call that implicitly +1s the right-hand val. I agree with earlier
comments that this is too common a syntax to delegate to something that
feels like a function call. I even wouldn't mind ..., but granted, it's
probably the = / == bug in new clothing and bound to shoot someone in the
foot...—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-287566556,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AIhJ6jWgPwndKaQvVjULlV_OoC6WDO0Cks5rnCeLgaJpZM4F4LbW
.
Oh, is this still in a bikeshedding phase? 😆
Reading back, I think I agree with @nikomatsakis to just make it ..=
and call it a day. The Similarity of ..
vs ...
is too subtle, and I'd rather see the ...
token be used without ambiguity for variadic generics (https://github.com/rust-lang/rfcs/pull/1935), where it fulfils a very distinct purpose that should lead to less confusion between the two.
I'm not sure if the desire for a more generalized syntax for all half-open variants is still there, but I think its not worth the effort to provide language syntax outside of ..
and ..=
.
I hate to even mention it because it's just more noise, but what about .: (dot colon) for inclusive?
I think @ssokolow made some great points re: UI/being clever. I'd argue that switching the direction of the third dot for the sake of tricking your eyes into seeing something else would probably be in that category.
I personally have _zero_ objection to there being a third dot, other than knowing it's bound to trip some people up eventually. But it's also dead easy to explain, so I'm not sure the onus should be on the language to design clever workarounds. Is '2 dots exclusive; 3 dots inclusive' really _that_ hard to grasp/debug?
In any case - who makes the final decision, and what's the next step to closing this off? 18 months discussing the third character probably _is_ bikeshedding at this point 😄
Agreed, ^ makes it look like a step.
IMO the inconsistency with other op=
operators is no problem because there is no way that we would ever have a ..= b
in the sense of a = a .. b
, since ..
is not an operator but syntactic sugar to construct a Range (and we don't have a general op overloading scheme where any op automatically gets a form of op=
, and ..
).
I'm not saying ..=
is clear without looking at the doc. But once People looked at the doc it's easier to memorize, and they will have to look at the doc anyway.
Given @nikomatsakis's nomination, I think we'll end up discussing it in next week's lang-team meeting, and likely making a call at that point. I agree that this just needs the lang team to make a call and end the bikeshedding.
I also worry that, even if I did recognize that
..^
was range syntax from its use in context, I'd have no intuitive idea whether it was inclusive or exclusive at first
Agreed. In fact, I've already seen this syntax in Perl 6 where it means exclusive, the opposite of the proposal here.
@solson That's a very compelling argument against it.
Perl seems to have fully general syntax, with .. meaning inclusive (on both
sides) and ^ on either side makes that bound exclusive.
On Sat, Mar 18, 2017 at 6:51 PM, Josh Triplett notifications@github.com
wrote:
@solson https://github.com/solson That's a very compelling argument
against it.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-287580739,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3n7Fnn1_t9BkYOhfS-7oCaGlVff2aks5rnF_5gaJpZM4F4LbW
.
So, I'm sorry to say, but when we discussed this issue in the @rust-lang/lang meeting, we failed to reach a consensus. @withoutboats in particular has some severe reservations that hopefully they can air for themselves.
Some major points that we discussed:
x ..= y
, that implies deprecating the existing x...y
pattern syntax.(x..y).inclusive()
or some such thing. However, that will not work in patterns (which would presumably stay as x...y
). This raises some questions of its own:match 3 { 1..3 => false, .. }
I think one thing that came out of this discussion was that it would be good to make a decision on all the conflicting factors at once. In other words, to adopt a decision that settles:
Before I begin this write up, I want to say that I felt very uncertain that at each moment in the conversation we were all actually talking about the same concept. For example, Niko says that "exclusive range patterns seem irrelevant right up until you want them," but I thought Niko and Josh were talking about inclusive range expressions at that time.
(And I think in the last part of Niko's post, the first bullet which currently reads "exclusive range expressions" should say "inclusive range expressions".)
About range expressions:
...
nor ..=
.inclusive(0..10)
(the name can be bikeshedded).About range patterns:
In other words, the only material change we should make is a libs change: add a function to the prelude.
Fundamental to my argument is the fact that iterating through an inclusive range value is an uncommon need. I think this is borne out by the fact that despite the RFC being accepted 18 months ago, we still haven't stabilized this feature - not because of implementation issues but because we aren't happy with the trade off.
I think inclusive range expressions are needed often enough that we should support a straightforward way of creating them, but not often enough that they overcome any of the downsides of the syntaxes we've discussed so far.
In particular, a downside every syntactic solution has shared is that none of them are really self-documenting. It is relatively non-obvious that 0...10
or 0..=10
is an inclusive range expression. Since they will be encountered relatively infrequently, this will be a stumbling block for users encountering them for the first time.
That is, if the user even notices that they aren't dealing with a "normal" ..
range. Of course, this is the big problem with ...
, but ..=
doesn't totally eliminate the problem. Its easy, when skimming through code, to miss that one =
character (or ^
or :
or whatever else). Not as easy as the extra .
, but I'm not convinced its noticeable enough.
I actually think that there is a "noticability vs obviousness" trade off between ...
and ..=
. I think its more obvious what ...
means (unless you're coming from Ruby, where the two syntaxes have the opposite meaning) than ..=
, but its definitely less noticable.
But I think a prelude function like inclusive(0..10)
is both more obvious and more noticable than any syntax we've discussed. Yes, its more characters to type, but the downside of that attribute is relative to the frequency of use.
It also sidesteps any of the parsing & tokenizing issues, and helps users with the (very annoying) range vs method precedence issue that requires them to write (0..10).filter
and such.
The main reason I think we shouldn't add ..
is that it introduces ambiguity around slice patterns, which I think are useful. I'd like to solve that problem by not having to solve it.
The second reason is that I think they're pretty bad style (I know other people disagree). For example:
if let 1..10 = x { .. }
I think this is confusing, because 10
doesn't match the pattern that contains it. Niko mentioned that this isn't really different from exclusive range expressions not yielding 10
, but I think the big difference there is that we have a great deal of Dijsktra-style historical precedent (and use case) for supporting exclusive ranges. Coming into Rust, I expected iterative & slicing ranges to be exclusive, but I didn't have that expectation for patterns.
It also has the "off-by-one" issue that ..
/...
in expressions has.
I know Niko has mentioned that he'd like to write:
match x {
0..10 => { ... }
10..20 => { ... }
}
But I would really strongly prefer to see:
match x {
0...9 => { ... }
10...19 => { .. }
}
Anyway, I was surprised that Niko meant that he sometimes finds these "absolutely necessary," so I'd like to hear more counterarguments. Since they have to be a constexpr, it seems much more like a 'nice to have' than an 'absolutely necessary' thing to me.
I definitely feel easier to sway on exclusive range patterns than inclusive range expressions.
Niko's mentioned that having both ..
and ...
in both expressions and patterns matters to him because of consistency. I'm basically entirely unmoved by this argument. Iterating through a range and matching on a range are not really analogous operations, and it makes sense that there would be differences between how we treat them.
Indeed, there isn't even an implementation connect: 1..10
produces a Range value, whereas 1...10
matches an integer value. Its not like they have a structuring/destructuring connection in the way that most of our symmetrical expression/pattern syntaxes do.
It seems more technically correct to call the patterns "domain patterns" than "range patterns" 🤓, which highlights the nonanalogousness.
@withoutboats
For example, Niko says that "exclusive range patterns seem irrelevant right up until you want them," but I thought Niko and Josh were talking about inclusive range expressions at that time.
For my part, I've found both exclusive and inclusive ranges necessary at various times (though I've used exclusive for both cases to work on stable Rust, and had to work around it with a +1
in the upper bound). I'd like to have them available for both patterns and expressions.
That said, personally, I would have no objections to using a function for inclusive ranges, except that I do want to have a way to write both inclusive and exclusive ranges in patterns, and I'd like those two syntaxes to not look confusingly similar as ..
and ...
do. And given a syntax for writing such ranges in patterns, I don't know that it makes sense to have a different syntax for such ranges in expressions.
In particular, a downside every syntactic solution has shared is that none of them are really self-documenting. It is relatively non-obvious that 0...10 or 0..=10 is an inclusive range expression. Since they will be encountered relatively infrequently, this will be a stumbling block for users encountering them for the first time.
I do agree with this. This comes up sufficiently infrequently that I'd have no objection to less compact syntax. I would, however, like to have some mechanism to write inclusive and exclusive ranges in both expressions and patterns.
I wouldn't mind, for instance, if writing an inclusive range pattern required a macro or similar.
@joshtriplett This is where I want to be really clear, when you say this:
I've found both exclusive and inclusive ranges necessary at various times (though I've used exclusive for both cases to work on stable Rust, and had to work around it with a +1 in the upper bound).
It seems clear you're talking about expressions, but the section you quoted was about patterns (I know you talk about patterns in your next paragraph, but I'm asking about the "necessity" issue which that paragraph doesn't address 😃).
The status quo for patterns is that we only support inclusive range patterns, of the form x...y
. Can you talk more about if/when you've found exclusive range patterns really frustrating not to have?
@withoutboats
Can you talk more about if/when you've found exclusive range patterns really frustrating not to have?
Something like
match offset {
0x0200 .. 0x0280 => { /* GICD_ISPENDR<n> */ }
0x0280 .. 0x0300 => { /* GICD_ICPENDR<n> */ }
0x0300 .. 0x0380 => { /* GICD_ISACTIVER<n> */ }
0x0380 .. 0x0400 => { /* GICD_ICACTIVER<n> */ }
0x0400 .. 0x0800 => { /* GICD_IPRIORITYR<n> */ }
}
vs
match offset {
0x0200 ... 0x027C => { /* GICD_ISPENDR<n> */ }
0x0280 ... 0x02FC => { /* GICD_ICPENDR<n> */ }
0x0300 ... 0x037C => { /* GICD_ISACTIVER<n> */ }
0x0380 ... 0x03FC => { /* GICD_ICACTIVER<n> */ }
0x0400 ... 0x07FC => { /* GICD_IPRIORITYR<n> */ }
}
I wouldn't say it's especially frustrating, but the first one certainly looks nicer.
@withoutboats For clarity, at various times I've wanted exclusive range expressions, inclusive range expressions, and inclusive range patterns. I can't offhand think of a time that I've cared deeply about having exclusive range patterns, though I also wouldn't object to them. But even if we don't have exclusive range patterns, I'd still it very confusing if inclusive range patterns use ...
and exclusive range expressions use ..
.
@petrochenkov I assume those were meant to end in F
, not C
?
I've run into the same case with hex range patterns, e.g. 0x8000...0x9FFF => /* body */
. I find that 0x8000..0xA000
has slightly more intuitive properties, like without having to think about it I immediately see that the size of the range is 0xA000 - 0x8000 = 0x2000
and that the next adjacent range starts at 0xA000
.
Dealing with the +1
required to see these facts in an inclusive range is a small difference I can live with, but exclusive ranges (both patterns and expressions) typically suit my work better.
@petrochenkov I can see why you would prefer exclusive ranges for hexdigits (I still might not, but this feels like a very YMMV deal).
How would we deal with the slice syntax ambiguities though?
@joshtriplett
I'd still it very confusing if inclusive range patterns use
...
and exclusive range expressions use..
.
This is how Rust works today and it doesn't seem to be a significant source of confusion?
@solson
I assume those were meant to end in F, not C?
No :)
(Those things are 32-bit and offset
is a multiple of four.)
@withoutboats
I can see why you would prefer exclusive ranges for hexdigits (I still might not, but this feels like a very YMMV deal).
I should note, this is not a strong preference, I'd still be okay with dropping both exclusive patterns and inclusive ranges (but not one of them, that would be too ridiculous).
How would we deal with the slice syntax ambiguities though?
Easily! PATTERN..
=> .. @ PATTERN
My main reason for wanting inclusive ranges to be simple is that I've run into situations more than once where I'm passing in a stop value in a variable and it's exactly the maximum value that one variable type can hold, so the only solutions to rule out overflow at compile time are:
+ 1
(assumes I'm not already on u64
and is inelegant)Building a habit of using inclusive ranges rather than adding + 1
to an algorithm where it has no inherent purpose is a non-trivial way to guard against encountering them again later in cargo fuzz
... and using a "make inclusive from exclusive" function with more characters to its name than + 1
gives the impression that inclusive ranges are an exceptional thing, rather than something you should be habitually using.
That's one of the biggest reasons I'm against it. It conveys the impression that inclusive ranges are a hack, to be used when exclusive ranges are demonstrated to have failed.
@ssokolow but that use case is comfortably covered by a prelude function. No one's taking the position that iit shouldn't be possible to create inclusive ranges, just whether or not they should have syntactic sugar for creating them.
I like @withoutboats idea of a prelude function. I think the other place where inclusive ranges might be more common is when you don't use integers, e.g., by specifying the bounds of a search on a btree (or similar data structure).
@withoutboats I edited a fair bit onto my post while you were responding, but the gist of what I added is that making inclusive ranges second-class citizens, syntactically (with a syntax longer than adding + 1
to an exclusive range), feels like a subtle discouragement against using them and a possible "I'll see you in cargo fuzz
later" footgun.
If nothing else, it's a teachability wart.
Rust isn't Python 3.x, with its unbounded integer support. Rust doesn't hide the hardware trade-offs from users, and I see the ..=
I prefer as just part of using u32
and friends instead of int
. (Especially given that overflow/underflow errors are the most common thing on cargo fuzz
's "trophy case" so far.)
EDIT: Please ignore any bits you only see in the e-mail notifications for this. I just woke up and i'm not firing on all cylinders yet.
I don't see inclusive(n..m)
as a discouragement at all... I would prefer to write it because it's a very clear construction that makes my code easier to read than both n..(m + 1)
and n..=m
(which I've come to consider unnecessarily weird looking when we could just say the word "inclusive").
n..=m
is more a teachability wart than inclusive(n..m)
IMO.
I think that for match there's a desire to have a complete coverage of the numeric range, rather than a specific type of pattern.
Presumably if there was a "continue from previous" syntax, it'd also solve the problem.
BTW, since only the first pattern matches, the starting number can often be omitted:
match offset {
0 ... 0x01FF => {}
0 ... 0x027C => { /* GICD_ISPENDR<n> */ }
0 ... 0x02FC => { /* GICD_ICPENDR<n> */ }
0 ... 0x037C => { /* GICD_ISACTIVER<n> */ }
0 ... 0x03FC => { /* GICD_ICACTIVER<n> */ }
0 ... 0x07FC => { /* GICD_IPRIORITYR<n> */ }
}
I admit that this may be a matter of perspective, but:
When I see ..
and ..=
, I think "Huh. I wonder why they have two syntaxes for such a small difference", which would then lead me to go looking for docs which could focus on "1..pivot
and pivot..end
" vs. "x..=y
where y
may be the maximum possible value".
Before I got experienced enough to be habitually thinking in terms of variable sizes, I'd just have used + 1
over inclusive()
(if I even went looking for it), because I've been using + 1
since primary school, it's short and easy to type, and my inexperienced self is used to working in languages like Python and JavaScript where addition causing overflow isn't something people worry about.
EDIT: ...and it's that conceptual distinction in point 1 which I think we should be focusing on. (ie. That "from X to pivot" and "from X to end" should be distinct in the programmer's mind.)
@pornel
This is horrible 😄
In this specific case the range start is really really important for a person reading the code, much more important than any inclusive/exclusive range issues.
@ssokolow it seems to me like what makes +1
an issue is not the number of characters but that you have to handle the potential for overflow. Also it doesn't communicate intent as well, and requires wrangling precedence-parens. These all seem much more important than the number of chars.
Its true someone who doesn't know about overflow might reach for +1
before creating an inclusive range, but this doesn't seem dependent on syntax. Both discovering that ..=
is a thing and that inclusive()
is a thing present the teachable moment for learning about why you would want inclusive ranges specifically.
@petrochenkov
I'd still be okay with dropping both exclusive patterns and inclusive ranges (but not one of them, that would be too ridiculous).
Could you expand on why you feel so strongly about this? I definitely see accepting exclusive patterns only as having a downside in that it might lead users to expect 0...10
as an expression, but it doesn't seem that bad to me.
Part of my issue with inclusive()
is that it's "just" a function and I worry that people will do things like going to the Rust syntax index, searching for "range" or "iterator" and then assuming an insufficient ROI for "See[ing] Iterators".
("I don't want to read a textbook on the off chance I'll find something useful... I just want to iterate over a range and get back to making progress.")
@withoutboats If we have inclusive(a..b), but we can't use it in match, it's not worth it IMO.
Most of the time when I'm using inclusive ranges, it's in match patterns!
So why can't we remove ...
and just use ..=
inside and outside of match?
@Boscop the proposal is not to remove ...
in match, but to leave patterns exactly how they are on stable.
@ssokolow seems like that can be resolved pretty easily with a note in the ..
section
@withoutboats But why not remove ...
in match and use ..=
instead, so it's consistent?
(so that it would be ..=
inside and outside of match)
For numeric range coverage in floating point, exclusive helps. For example, one of these boundaries is fully-covered, but not the other:
match x {
0.0...3.141592653589792 => 1,
3.141592653589793...6.283185307179585 => 2,
6.283185307179586...10.0 => 3,
_ => 4,
}
And writing it with overlaps instead feels icky (and morally similar to the "just start everything from -∞ since the arms are ordered" above). See also datetimes, like how ISO8601 allows T24:00, since a day is [00:00, 24:00), not [00:00, 23:59:59]. Or strings, or rationals, or ...
Given a choice of only exclusive or only inclusive, I'll take exclusive every time.
(Aside: The 1 <= x <= N
pattern for indexing is actually better done as 0 < x <= N
, which is also exclusive--though half-closed instead of half-open--for the same reasons Dijkstra said, just switched around for 1-based indexing instead of 0-based.)
This operator would have been super helpful to the clamp function I proposed here: https://github.com/rust-lang/rfcs/pull/1961 however since this hasn't been stabilized I'll probably have to modify that proposal to use two min and max arguments instead. InclusiveRange is very helpful when working with floating point values too so we don't have to do something like:
0.0..1.0+EPSILON
I'm not even sure if that's correct, but right now it's very difficult to declare an inclusive range of floating point numbers.
The correct way to do this would be with nextafter
, not epsilon. i.e. x...y == x..nextafter(y)
. ieee754 crate exposes such functionality
Whoops, I made a comment about the inclusive(a..b)
syntax but I realized it wasn't true. Anyway, I like it but I hope we can bikeshed a better name.
@durka The name seems clear to me. Can you elaborate on your concerns with it?
So for inclusive ranges we'll have a ... b
in match
and inclusive(a..b)
outside of match?
Why can't we be consistent and have a ..= b
everywhere?
Is there anything blocking ops::{RangeInclusive, RangeToInclusive}
from being formally stabilised?
Right now it seems like the major blocker is the debate on the syntax, although regardless of that debate, I'm pretty sure that inclusive ranges will exist regardless of what the end syntax is.
It'd be nice to have these types for stable libraries to implement, and then consumers of these libraries can decide to enable the feature flag if they want to use the special syntax.
One thing I noticed about RangeInclusive
the other day: it's a pain to code against if you're trying to accept a range. The clamp thread was talking about allowing .clamp(1...9)
, but the code ends up looking something like this:
fn clamp(self, r: RangeInclusive<Self>) -> Self where Self : Sized {
match r {
RangeInclusive::Empty { .. } =>
panic!("Cannot clamp to an empty range"),
RangeInclusive::NonEmpty { start, end } => {
assert!(start <= end, "Cannot clamp to a degenerate range");
if self < start { start }
else if self > end { end }
else { self }
}
}
}
Needing to deal with RangeInclusive::Empty
there feels unnecessary since the goal is just to accept the pair with nice syntax. If it weren't an enum, it could just be fn clamp(self, RangeInclusive { start, end }: RangeInclusive<Self>)
, and much cleaner.
Unfortunately I don't have a good answer for what to do about it, though, since requiring .into_iter() on inclusive but not exclusive would also be unfortunate...
Random idea: Seeing how the ...
pattern syntax is not really related to the inclusive range types, we might consider deprecating its syntax in favour of one unrelated to the ...
proposed ones here.
Since a inclusive range pattern is more closely related to the |
pattern, |...
could be a good candidate:
match {
1 | 2 | 3 => ...
1 |... 3 => ...
}
The end result would be that inclusive and exclusive range syntax have no 1:1 relation to pattern matching anymore, and thus don't need to be consistent with it.
@scottmcm
Unfortunately I don't have a good answer for what to do about it, though, since requiring .into_iter() on inclusive but not exclusive would also be unfortunate...
Maybe we could add a helper like range.nonempty()
that panics for an empty range, and returns a pair?
Alternatively we could implement IntoIterator
for RangeInclusive
and it wouldn't be too far off IMHO.
What use case does RangeInclusive::Empty serve? Why does an inclusive range need the ability to represent an empty range? How would you write one? (Note that you can already write things like "the range from 5 to 4, inclusive", which iteration would presumably treat as empty.)
@joshtriplett the main case is overflow, although there are ways of solving that problem.
@joshtriplett start > end
actually doesn't work to represent the empty inclusive range in one important case: 0u8...255u8
. When you get to 255u8...255u8
and try to .next()
it, you would either get a panic or wrap to 0u8...255u8
, which isn't empty. So instead we switch to the Empty
variant at that point.
@solson Ah, I see. Yeah, that's a pain, and it is one of the biggest use cases for inclusive ranges.
(I'm assuming you mean 255u8
in all of those cases.)
(I'm assuming you mean 255u8 in all of those cases.)
Yeah, thanks. Edited.
@solson however, this case can be rectified by swapping 0 and 255 in that case
@clarcharr That's true. Since .next()
always has to special-case the generation of an Empty
range, it could always generate a range that'll get treated as empty.
Personally I like that way better because it doesn't involve the empty range.
So this thread has been going on for one and a half years now. I just finished reading through all of it and would like to post a concise recap for newcomers and to hopefully help us come to a decision:
What Rust Stable does today:
0..5
represents a half-open range, [0, 1, 2, 3, 4] that cannot be used in pattern matching
0...5
represents a closed range [0, 1, 2, 3, 4, 5] that can only be used in pattern matching.
Several forms have been proposed for an additional closed range syntax that can be used outside of pattern matching. I will cover each one and the benefits and drawbacks of them.
0...5
Pros: Consistent with existing pattern matching syntax, makes the language feel more cohesive assuming no changes are made to the pattern matching syntax.
Cons: Easy to mistype and cause off by one errors, also easy to misunderstand the intent due to other languages using this operator to communicate different concepts.
0..=5
Pros: More difficult to mistype, more semantically clear
Cons: Inconsistent with existing pattern matching syntax. May cause users to ask: Why is it ... here but ..= here?
0..^5
Very similar to ..=
but has an additional con in that it looks like an exponentiation operator.
inclusive(0..5)
Pros: Exceedingly semantically clear. Won't be mistyped.
Cons: Kind of long. Also inconsistent with pattern matching syntax.
0....5
Pros: Avoids the mistyping issue of ...
as well
Cons: Poor semantics, inconsistent with pattern matching syntax and similar to pattern matching syntax.
[0..5]
Unusable. brackets already have syntactic significance in the language.
0..<=5
Unusable. Conflicts with existing syntax to compare a value to a range.
Every option listed that has a con of "inconsistent with pattern matching syntax" could be improved if we were to change the pattern matching syntax, but that route has backwards compatibility issues. Alternatively we could also make ...
and (pick your syntax here) equivalent within pattern matching in order to avoid breaking backwards compatibility, but prevent the use of ...
outside of pattern matching. Perhaps we could also update the style guide to discourage the use of ...
in pattern matching if we were to go down that route.
There has also been some discussion about creating a more general purpose range syntax that would allow you to make both the upper and lower bounds inclusive or exclusive but we probably don't even need that syntax as having half-open and closed range likely covers over 99.9999% of use cases.
I tried to represent this discussion as best as I could. If you feel I didn't express your point adequately please let me know so I can update this.
@Xaeroxe Thanks for the excellent summary.
Maybe it's possible to have a tool (maybe a plugin of rustfmt) that auto converts sources from using ...
in pattern matching to the new syntax (e.g. ..=
) when that's decided.
That way we won't be held back by "all the code written the old way"..
But it has to be invoked with intention from project authors and there should be a notification / headline on all rust mediums so that everyone is aware of the change.
With those things I think it's no problem to change the ...
syntax in pattern matching to the new syntax.
I think one thing we could do to punt on the syntax issue is just stabilize RangeInclusive, as @clarcharr suggested. This will unblock people at least.
We could also add the inclusive
function (either just to std::range
or possibly to the prelude as well); this doesn't preclude having a first-class syntax some day but it makes it much more convenient to construct a RangeInclusive right now.
However, all of these seem like libs decisions so I don't know if the lang team has jurisdiction to decide to stabilize/add these items 😅.
I personally would prefer to convert RangeInclusive
itself into something that doesn't have an Empty
variant, then make an IntoIter
have the variant. It makes more sense to me considering how it's essentially impossible to construct an empty range without manually Empty { at }
.
Alternatively, swap MAX
and MIN
on the one edge case that I mentioned. That makes it harder to write more general code involving RangeInclusive
, but it seems like a reasonable solution to the problem.
Regardless of what happens, I'm very much in support of stabilising the type in the standard library, which will exist regardless of syntax concerns. It allows libraries to write stable code that allows slicing with inclusive ranges, so that by the time that the syntax is stabilised, people have already implemented the code that uses the types. The syntax and types are under different feature flags for a reason.
@scottmcm I agree that should probably be a separate thread at this point.
In an attempt to keep the syntax discussion moving forward I'm going to offer the route I would prefer to take.
I'm most fond of the ..=
syntax, so I feel we should add this syntactic sugar as an equal to ...
in pattern matching and as an InclusiveRange outside of pattern matching. Then in order to keep the language consistent we should try and migrate users and their code over to the ..=
syntax in patterns, using announcements and automated tooling. Once we feel we've migrated enough users away from ...
we can make its use a compiler warning and then eventually (possibly even years from now) a compiler error.
It's been 24 days since my proposal was provided and no comments have been given. Should we move forward with that plan?
EDIT: On mobile I didn't see the thumbs downs, so I didn't realize there was dissenting opinions.
@Xaeroxe I don't think so. I don't agree that deprecating ...
in favor of ..=
is a good choice for reasons I have already posted in this thread. I didn't reply to your specific proposal because the same idea had already been put forward and discussed. The only thing that's clear here is that we don't have consensus. I think many of us feel fatigued by the discussion.
EDIT: To be more specific, I do not want to deprecate ...
in favor of ..=
because:
..=
I don't think it would be worth the deprecation cycle just to get inclusive range expressions on stable...=
, I think it is non-obvious and unclear; it has the advantage that its less likely to lead to off by one errors but that doesn't mean a user, seeing it for the first time, will know what they're looking at.(I shift views a bit during this comment, I decided to leave it all so others could see my thought process and why I came to the conclusions I did)
I can't really comment on the cost of deprecation due to my lack of experience with such things. However I am still in support of ..=
.
I think at a certain point we have to be willing to ask users to learn something. Programming as a whole is something to be learned, specialized syntax in general always comes at some cost to semantics. I don't expect people to recognize syntax at first glance, ..
is just as bad in this regard.
However having an over-abundance of specialized syntax can result in a language looking closer to brainfuck than what we want Rust to be. So we have to be careful that the cases we choose to turn into special syntax are actually common enough to be worth it. To be honest now that I've typed this out I'm not entirely sure this case is worth that. The need for inclusive ranges is not high enough to warrant a syntax.
However the inconsistency still bothers me. Having an inclusive function and ...
in pattern matching sort of feels like having both grey and gray in the English language. When we have an opportunity to standardize that a part of me feels like we should. There are also actual logistical problems in making that change though. If we were to ever design a Rust 2.0 (That might be insane, I have no idea) maybe we ought to revisit this but I guess for now having both grey and gray is good enough.
I am in support of using an inclusive
function for instances outside of pattern matching and ...
in pattern matching at this time.
@withoutboats
If you never deprecate anything, you end up with the union of all (more or less) bad decisions like C++. This forces newbies to learn even more because things aren't as consistent as they could be.
So sometimes it makes sense to deprecate stuff to make way for the better solution.
that doesn't mean a user, seeing it for the first time, will know what they're looking at.
This is the case with any syntax. We (who know Rust) have this bias because to us, Rust code looks obvious but anyone who comes to Rust from another language has to look up syntax often. It doesn't change if we use ..=
or ...
or any other syntax for inclusive ranges because e.g. other languages use ...
for exclusive ranges so they have to look it up anyway.
It shouldn't be our goal to only introduce obvious looking features:
If we wanted to have only features that look obvious, we couldn't have most of the existing features in Rust. Newbies HAVE to look at the doc anyway, what's so wrong about that? The doc is good!
As @withoutboats said, I think many of us are weary of this discussion; I for one have been sitting it out for a while. That said, I took some time to re-read (thanks for the summary @Xaeroxe!) and revisit my thoughts here.
IMO, the most important problem right now is the incomplete combination of syntaxes we provide: ...
in patterns, ..
for expressions. This pair of syntaxes strongly leads you to expect that ..
would work in patterns and ...
in expressions -- and they don't. I believe that this is not a tenable design in the long term, and really want to fix it.
I also think that, given that we've provided syntax for both kinds of ranges, it's probably best to continue doing so. That leaves us with a couple of options:
Stabilize ...
in expressions and add ..
to patterns. This is the least painful change, but has well-discussed downsides around off-by-one errors. Still, other languages have this combination and have not, to my knowledge, suffered terribly with such errors.
Deprecate ...
in patterns and expressions, add ..=
to both, and add ..
to patterns. Resolves the downsides above, is more painful (because of deprecation), and opens the door to other kinds of ranges (like left-exclusive) in the future.
At this point I'm largely rehashing discussion, and honestly, I don't think there's a lot more to be said at this point; I think we've exposed the tradeoffs and just need the lang team to do an evaluation and reach a decision. But the main thrust of my comment is the initial point, about why I think something needs to change here (and why just adding inclusive
isn't enough, IMO).
Still, other languages have this combination and have not, to my knowledge, suffered terribly with such errors.
I worry about selection bias here. We have no reliable way to judge whether the problems are minor or whether they just rarely come to our attention.
Also, using ...
feels like it runs counter to the "avoid footguns unless the trade-offs are too severe" aspect of Rust's design philosophy and emerging best practices. (eg. The advice to create new enum
types rather than using bool
so it's harder to mix up your function parameters.)
For example, C has proved that assignment in if
statements is useful if a language doesn't have while let
, but it's been recognized that typing =
when you meant ==
is enough of a footgun that languages like Python refuse it, even when there exist situations where the only alternatives are uncharacteristically ugly.
Going in the other direction, I actually had Clippy catch a case like this when I was coding tired about a week ago...
foo == bar; // This should be `foo = bar;`
...which seems like a perfect example of the kind of "pressed the key one too many times" mistake that we're worried about here... and, unlike with "==
has no effect", there's no good way to lint for ..
vs. ...
.
@ssokolow To be clear, I'm fine with ..=
too; i don't think the deprecation will be so painful, and it's a very easy automatic fix. Mostly I just want to get this issue resolved.
I can probably live with stabilizing ..
/...
(which is already implemented on nightly). I agree with @aturon that the language as it exists suggests this will work.
As I've said, I'd really like to stabilise the libstd type when rust-lang/rfcs#1980 passes without requiring a syntax discussion.
Alongside what @aturon pointed out, perhaps it'd be useful to merge the tracking issues for both this and exclusive range syntax? Perhaps make a separate tracking issue for the type and use that to stabilise the type before the syntax.
I'm re-nominating for lang team discussion. I think it's very unlikely that significantly new ideas or tradeoffs are going to arise at this point. We need to reach a decision about the tradeoffs that have been laid out.
On May 18, 2017 9:55:30 AM PDT, Aaron Turon notifications@github.com wrote:
I'm re-nominating for lang team discussion. I think it's very unlikely
that significantly new ideas or tradeoffs are going to arise at this
point. We need to reach a decision about the tradeoffs that have been
laid out.
I'm going to be out for next week's meeting due to a conference. In case this gets discussed in next week's meeting, I'll voice my support for any solution other than ...
, whether ..=
, inclusive
, or other.
It would be worth discussing rest/spread syntax while we are at it.
I vote ..
for exclusive range, ...
for inclusive, ....
for rest/spread.
take_range(..max); // exclusive range
take_range(...max); // inclusive range
take_multiple_args(....tuple); // spread
if let 0..10 = get_num() {} // exclusive range pattern
if let 0...9 = get_num() {} // inclusive range pattern
if let [first, ....rest] = get_slice() {} // rest pattern
I'd vote ...
for a rest slice for two reasons:
People are likely to already be familiar with ...
meaning "rest" from languages like CoffeeScript (CoffeeScript uses rest...
in function signatures for varargs and calls them "splats") and English (Using the correct Unicode codepoint or not, it's three periods which look like an ellipsis, not four, and people do a decent job of understanding the ellipsis to approximately mean "and there's more but we're not saying it out loud" in English, purely from encountering it in use.)
Unless I'm missing something about Rust's syntax, using ..
and ..=
for ranges but ...
for rest slices would mean that, in every situation, typing ...
when you meant ..
or vice-versa would still be a compile-time error.
For this, I'm operating on two assumptions:
That using rest syntax alone (outside a sequence-unpacking context) would be disallowed as a typo-prone no-op. (ie. for _ in ...rest
would be the same as for _ in rest
if allowed, but almost certainly a typo.
That using an open-ended range as a pattern to be assigned to would be invalid. (ie. let [first, ..rest] =
would be invalid.)
@ssokolow triple dot already means a range in pattern so that would be a breaking change to change it to rest syntax.
@phaux Point. I'd forgotten about that for some reason.
Something to hang off the "if we ever make Rust 2.0" tag, perhaps.
triple dot already means a range in pattern so that would be a breaking change to change it to rest syntax.
However, the ...
in patterns must have an expression on both sides, I believe (a...b
), whereas the rest syntax uses ...
as a prefix, no?
The lang team discussed this feature again in our meeting today, and arrived at roughly the following matrix:
Supporting ...
in patterns and ..
in expressions, and nothing else, is an untenable design; it leads users to expect syntax to work that won't.
Allowing ...
and ..
in both places would help, but the off-by-one problem is a real concern; none of us are eager to receive reports of people spending hours tracking down an errant period.
Moving to ..=
and ..
is less aesthetically appealing, but avoids the very real practical problems above. We can also roll it out in a very gentle fashion: first introduce ..=
as an alternative syntax (which rustfmt can rewrite to), and only after it has become idiomatic, deprecate ...
.
In case you can't tell, the team feels that ..=
and ..
is our best path forward. This issue has also been discussed to the point that it's very unlikely that new arguments will be raised, so we're ready to take the plunge: we're looking for someone who would like to implement ..=
notation for both patterns and expressions, and subsequently we'll go to FCP!
To that end, we're looking for someone (or some people) to drive the implementation forward. Probably this can be done in a number of smaller PRs:
..=
to the parser as an alias for ...
and convert the existing tests....
should keep working, of course, and we want this to be a "silent" deprecation, so there's no need to issue warnings etc (yet)....
patternsDotDotEquals
token (here is the existing DotDotDot
) and then modifying the parser to accept DotDotEquals
everywhere we now accept DotDotDot
(example)ripgrep DotDotDot
is your friend here ;)...
in expressions was never stable, so we can just change that over to ..=
and kill the older supportJust to note that, when implementing, the syntax extern fn foo(a: u32, ...)
should not be changed to ..=
😆
Tempted to take it as it looks like a nice first step into the code but i'm not going to have much time in the next 2 weeks so if anyone want to pick it up, feel free!
I'm personally more in favour of ..^
than ..=
, as a operator= b
is already, for some operators, an alias for a = a operator b
. ^
on the other hand is used much less, only for XOR, so it should cause less confusion.
..
yields a different type than the type of the left and right operands for it, so I don't see that being a problem for ..=
. Context also helps identify the purpose of the operator, something like +=
returns ()
so it can appear as its own statement, typically on its own line. Whereas ..=
returns InclusiveRange so it will appear as an expression inside other instructions. Furthermore as mentioned above ..^
looks like it has more to do with exponentiation than a range.
@Xaeroxe ^
has nothing to do in Rust with exponentiation (although it is used as such in other languages), while +=
, -=
, *=
all exist in Rust. In fact even ^=
exists.
But idk, its fine to have ..=
as well.
Since #42134 was merged, can we move to stabilise the library struct now? I figure that we're going to want to wait for the syntax to settle before stabilising that, although like I said earlier, it'd be helpful to stabilise the struct sooner.
@clarcharr I believe we've come to the decision to stabilize that structure as well as the ..=
syntax so we just need some PRs to actually do that.
EDIT: Woops I was wrong. We're implementing it in the compiler, stabilizing the syntax will need to wait for the Final Comment Period to elapse. I think we should stabilize the structure at this time but that's also not my decision to make.
I stopped following the subject long ago, when it seemed to me that ...
was making consensus. ..=
seem just awkward to me since it just look like an affectation at first sight.
But much more important, I don't think that this bikeshedding is important enough to change the current pattern syntax.
this syntax sucks!
= have meaning of assignment, even in +=, -=, *= model, but ..= is about getting something. so confused and ugly.
like array index start at 0, '...' is good and beautiful, people will get used to it.
@zengsai
..=
is conceptually derived from operators like ==
and <=
, which already exist in Rust and indicate equality comparison, not assignment.
(ie. 1..=5
is shorthand for "Range over values 1 <= x <= 5
" as opposed 1..5
meaning "Range over values 1 <= x < 5
")
EDIT: Also, it's not a matter of "getting used to it", it's a matter of which is more difficult to mis-read or mis-type when you're tired or distracted. Off-by-one errors like that are famously footgun-y.
I've actually accidentally typed ...
when I meant ..
already. Thankfully, I always target stable Rust, so the compiler caught it. (However, I can't remember whether it was a muscle spasm or muscle memory assuming I wanted to type a pseudo-ellipsis because I do a lot of non-fiction writing.)
@zengsai ...
is too subtle, looks too similar to ..
It'll be really abnormal for a language to have different number of dots meaning different things
Removing a foot gun is more important than being aesthetically appealing.
Newcomers: I've made a TL;DR, because this is a very long thread. You can read it here: https://github.com/rust-lang/rust/issues/28237#issuecomment-296310123
speaking to " Off-by-one errors like that are famously footgun-y", I have to say: typo issue should not be used as the key reason of this change.
look at this comparison, ".." and "..." vs "=" and "==", is that means we should found other syntax for "==" to avoid user's typo issue? if "=" and "==" is fine, why ".." and "..." can't ?
I've been follow rust-lang more than two years. ant it been used in many of my private project. I love it, but this syntax really let me feel uncomfortable. not only the aesthetically appealing, it also affect the writing and reading fluency of code.
@zengsai it was my understanding that it's not primarily about when _writing_ code, but when reviewing, reading and understanding code. If you aren't looking for it, you could more easily miss ..
vs. ...
being an off-by-one error, whereas ..=
definitely stands out more and won't have this problem.
I'm not sure I like the ..=
syntax either from an aesthetic point of view, but I can see where everyone is coming from about ..
and ...
reading very similarly.
@zengsai Because =
vs. ==
has a solution that isn't available to ..
vs. ...
.
Sure, you can confuse =
and ==
in C... but that's a recognized footgun and more modern languages like Python and Rust resolved that:
if x = y
rather than if x == y
(This is the fix Rust and Python made)x == y;
rather than x = y;
, I got a warning about an expression that has no effect.There's no way for the compiler to even recognize that a ..
vs. ...
confusion is fishy, let alone wrong.
@zengsai ..
and ...
evaluate to types which implement all the same traits and can be used essentially interchangeably (this is by design). The off by one error is a very real issue.
I want to be clear that I also hate this syntax, so much so that I don't agree with @aturon's comment that the current situation where we use ..
and expressions and ...
in patterns is "untenable;" in fact, I think its preferable to have the disconnect than to have this syntax.
But its clear we'll never have consensus on this, that there are no new arguments to be had on this thread, and that the majority of actively invested users (as well as the rest of the language team) favored this change. So I'm standing aside so we can have the issue resolved.
If its any consolation in my experience the vast majority of use cases never need really need this feature, and so it won't be appearing all over your code.
Actually, I'm still really unhappy about deprecating ...
in patterns, and I'm basically just going to grit my teeth so that this issue can be resolved. The entire lang team agrees that having both ..
and ...
expressions would be a terrible wart, leading some users to spend hours debugging a totally silent typo issue.
The rest of the lang team feels strongly that having a disconnect between expressions & patterns is unacceptable, and while I don't agree, we've all made our arguments and I don't think being at a stalemate forever is healthy.
@ssokolow Sorry, I still can't agree with you. compiler can warning you this kind of typo, but what if you type "x=5" at where it should be "x=6" ?
Compiler is not the perfect one to avoid typo, the programmer is.
if this change have no side effects, I totally agree. but this let the language lose aesthetic, writing and reading fluency, which is not worth for just in my personal opinion.
Today's rust already encounter reading and writing problem, the more uncommon and counterintuitive syntax the more learning curve, don't make it another Perl.
@withoutboats We argue with it, cause we love it. Maybe this argument is too late, and can't change anything, just...
@zengsai
There are certainly trade-offs to be made, but I get the impression we're never going to agree on the best balance point between those.
Regardless of personal taste, I see ..=
as a tiny price to pay to avoid what may be a very large amount of time wasted trying to track down bugs.
(My area of focus is UI/UX design and one of the biggest rules they teach you is that you should aim to make the ease of making a mistake inversely proportional to the harm and/or time-to-fix that it will cause.)
In my small opinion, 5
vs. 6
is much more obvious to see than ..
vs ...
.
@zengsai I'm receiving too many emails just for this conversation, how about moving to rust IRC channel to express your feelings better?
@daiheitan 这是正常交流,如果不喜欢,建议你取消定阅该邮件。
When I first looked at Rust, I was really surprised about the ..
syntax. (1..10).sum()
is... the sum of integers from 1 to 9? If you present this syntax to a newcomer and ask him what it means, it's very likely that he'll expect it to go from 1 to 10. And he would be right to do so, since you'd expect symmetric syntax to express symmetric ranges. Why should the 1 be included but not the 10?
Adding another asymmetric form ..=
for a symmetric range makes this even more visually inconsistent. One might ask why the left side needs no =
to be inclusive, so it would be =..=
for inclusive ranges on both sides.
Something like the Swift syntax (..<
for exclusive and ...
for inclusive) would avoid these problems and improve readability. You can easily guess the meaning correctly even if you're completely new to the language, so there's one less thing to remember. I would have expected something like this to be chosen, especially considering Rust's push towards newcomer-friendliness.
@rkarp To some extent, that's probably down to pre-existing bias.
For example, there's a lot of Python influence in Rust and Python uses the same kind of half-open ranges. (ie. Python's range(1, 5)
or myList[1:5]
both have the same behaviour as Rust's 1..5
)
The justification for that in Python was tied to there only being one syntax and having a half-open syntax made it easier to do things like head, tail = myList[:pivot], mylist[pivot:]
.
That said, given that Rust has passed 1.0, there are fairly strict rules about not breaking existing code. ..
for exclusive outside of patterns and ...
for exclusive inside patterns have to remain valid.
..=
works because you can accept ..
(exclusive) and ..=
(inclusive) everywhere and treat ...
as a deprecated synonym for ..
in patterns.
Just saying, having ..=
as a token is technically an incompatible change, as it affects :tt
rules in macros. Probably wouldn't be problematic, but you may want to check to be sure if somebody doesn't do that, considering it may as well be a quiet change. (the alternative is allowing .. /* why not */ =
, which I don't think is a good idea)
@ssokolow Technically, it's type error in Rust, not parse error. a = 2
is an expression that returns ()
, and if
expects bool
, so they are clearly incompatible.
The rules are slowly changing. Technically ..=
are 3 tokens where the first 2 have no space after them.
@eddyb: I do admit, I don't entirely understand it. Is there a way to introduce a new token without affecting how :tt parses it?
This code currently reports "Rule 2" and "Rule 4". This change if I understand it correctly would change it into "Rule 2" and "Rule 5".
macro_rules! ex {
( . . ) => { "Rule 1: . ." };
( .. ) => { "Rule 2: .."};
{ . . = } => { "Rule 3: . . = " };
{ .. = } => { "Rule 4: .. = " };
{ ..= } => { "Rule 5: ..=" };
}
macro_rules! show {
( $($t:tt)* ) => { println!("{}", ex!($($t)*)) };
}
fn main() {
show!(..);
show!(..=);
}
No, it's a change in how tokens are defined. @jseyfried can explain the consequences better.
Technically, it's type error in Rust, not parse error. a = 2 is an expression that returns (), and if expects bool, so they are clearly incompatible.
@xfix Thanks. I wasn't paying proper attention last time I made that typo and I'm still not completely used to thinking in terms of expression-oriented languages.
I've adjusted my comment.
I don't like the ..=
syntax and I am against of deprecating ...
in patterns. If we are afraid of the typo error, we can always use the struct RangeInclusive { start, end }
rather than ...
or ..=
for its shorthand.
Potential ambiguity I just realised:
if let 5..=x { ... }
@clarcharr Looks like it doesn't compile though.
rustc 1.17.0 (56124baa9 2017-04-24)
error: unexpected token: `=`
--> <anon>:3:13
|
3 | if let 5..=x {
| ^^
error: aborting due to previous error
@clarcharr @kennytm
After we introduce ..=
as one operator, the error would be:
error: expected one of `::` or `=`, found `{`
--> <anon>:3:13
|
3 | if let 5..=x {
| ^
error: aborting due to previous error
Just like now with let x=10; if let 5..x {}
So this "ambiguity" wouldn't compile and would thus be no more of an ambiguity than if let 5..x {}
.
@Boscop The point is, currently if we treat 5..
as a pattern like Some(5)
, then if let 5.. = x
would be similar to let Some(5) = x
, and the latter compiles. My comment shows that 5..
is not a pattern, so there is no compatibility hazard from if let
for introducing ..=
.
If we do introduce this feature and allow both ..=
and ..
in patterns, if let 5..=x
should always be if let 5.. = x
and it should compile: if let (single expression)
doesn't make sense, so I don't believe there's any ambiguity.
@kennytm so we both showed for 2 different ambiguities that they won't compile. (It wasn't clear which one he meant. It was kinda ambiguous, heh.)
@daboross Like kennytm said, 5..
is not a pattern and doesn't compile, even now. And this won't change if we add ..=
as an operator. There is no ambiguity. The thing that's important is that now, ..=
can't be parsed as one operator but once we can parse it as one operator, there is no ambiguity.
5..
is not a currently valid pattern, nor do we have half-closed interval patterns. That being said, lack of half-closed interval patterns is something that is often being seen as a source of inconsistency when compared to range expressions and might end up being implemented for patterns as well. We also might want to add ability to omit one side of the interval in patterns later on for e.g. exhaustive integer matches.
So while @clarcharr’s observation is not necessarily pointing out ambiguity when current grammar is considered, it is certainly an ambiguity when considering potential future expansions which might conflict with the proposal to use ..=
for patterns.
With that in mind I would suggest to use some other symbol here. ^
seems fairly okay to me, as it is only used in an expression context currently IIRC.
(This is another reason why I think it's a good idea to move to stabilise the struct now, and the syntax later.)
@nagisa there's no conflict regarding if let
if the token ..=
is added before RangeFrom pattern is supported. One could add a space between the ..
and =
to disambiguate, just like x <- y
(placement in) vs x < -y
(less than + negative).
(Since ^
is the exclusive-or operator I find it ironic to use ..^
for inclusive range)
In the thousands of ranges I've written, quite a few of them fully closed, maybe even one or two half-open the wrong way around, I still don't really see the need for any extra syntax. 1..(n+1)
isn't that hard to write.
That does not work for end = T::max_value().
On May 29, 2017 16:33, "Diggory Hardy" notifications@github.com wrote:
In the thousands of ranges I've written, quite a few of them fully closed,
maybe even one or two half-open the wrong way around, I still don't really
see the need for any extra syntax. 1..(n+1) isn't that hard to write.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-304662258,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AApc0ipynL_qrZqUBldOJOX046RNY2H_ks5r-skwgaJpZM4F4LbW
.
I would prefer a method on half closed ranges to ..= though.
On May 29, 2017 16:35, "Simonas Kazlauskas" s@kazlauskas.me wrote:
That does not work for end = T::max_value().
On May 29, 2017 16:33, "Diggory Hardy" notifications@github.com wrote:
In the thousands of ranges I've written, quite a few of them fully
closed, maybe even one or two half-open the wrong way around, I still don't
really see the need for any extra syntax. 1..(n+1) isn't that hard to
write.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-304662258,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AApc0ipynL_qrZqUBldOJOX046RNY2H_ks5r-skwgaJpZM4F4LbW
.
I really wish we'd stabilize the traits and types already with a method to construct them because I'm pretty sure there's no disagreement over that. Please don't let bikeshedding over the syntax hold off stabilization of the core functionality itself.
That does not work for end = T::max_value().
Do you really want to iterate over 2^64 values, or even 2^8 values? If you only want to iterate over the last few values of e.g. a u32
, it's often easier to add one inside the loop. And if you really want to iterate to 2^B-1
for some obscure reason, then you can always stick if i == END { break; }
at the end of a loop.
Just because our standard unsigned integers support 2^32 or 2^64 values doesn't mean the highest representable value gets used very often. And if you're doing something with u8
, don't forget that it's probably easier for the CPU to use u32
for a counter anyway.
But even if I don't see a need for closed range generator syntax, I can't refute the team's arguments for change. Oh well :/
How about these four to encompass all inclusiveness options?
1...10
1>..10
1..<10
1>.<10
I imagine one should be able to understand what they imply without further explanation, given that the inequality operators have been hammered into everyone's mind from kindergarten onwards... dunno if these would be a pain in other ways, though.
Alternatively, we could switch the inequalities:
1...10
1<..10
1..>10
1<.>10
... just so that the last one wouldn't look like an angry Cartman.
@vegai and what about the existing a..b
syntax which is certainly not going to be removed? No, I don't think suggesting new syntax options is going to solve anything.
Hmm, so there have been two "potential ambiguities" raised up:
if let 5..= x
-- as noted, this is not really an ambiguity now, but it could sort of be. On the other hand, this sort of ambiguity arises frequently, and is addressed by the tokenizer. That is, if we consider ..=
as a token in our parser, it's not ambiguous even if we add 5..
patterns in the future; you simply need to write a space between the ..
and the =
(e.g., let 5.. = x
).macro_rules!
back-compat via some special-case code if necessary; in any case, I think there is no avoiding this sort of problem except by using ...
.Before stabilizing inclusive ranges, issue #42401 should also be fixed imo, as after stabilization the fix would be a breaking change.
@est31 good point, I added that to the checklist at the head of the issue
Checked str slicing is itself unstable and fixing that issue does not block inclusive ranges at all.
Oh, @nagisa is right, it has nothing to do with range syntax. Can you remove it from the head again? My mistake. The bug is rather in the implementation of get
, and therefore guarded by the str_checked_slicing
feature:
impl SliceIndex<str> for ops::RangeToInclusive<usize> {
type Output = str;
#[inline]
fn get(self, slice: &str) -> Option<&Self::Output> {
if slice.is_char_boundary(self.end + 1) {
Some(unsafe { self.get_unchecked(slice) })
} else {
None
}
}
[...]
removed
Another checkbox update: PR https://github.com/rust-lang/rust/pull/42134 applied the https://github.com/rust-lang/rfcs/pull/1980 tweak
Bump: any way we can get at least the structs stabilised for 1.20?
How about we borrow the famous goes-to operator from C++ for the inclusive range syntax? 😛
@nikomatsakis and @rust-lang/libs (I don't know how to tag this), how would you feel if I submitted a PR that moved the structs to a different tracking issue, so that we can talk about the stabilisation for them there? I think that this would be a good way to move forward on stabilising those sooner while we wait for the new syntax to be implemented/settled.
cc @rust-lang/libs (only people in a team can tag theirs or other teams)
Thanks, @eddyb!
@clarcharr Based on https://github.com/rust-lang/rust/pull/42275#discussion_r119211211, I suspect it would be appreciated.
Am I too late for the train for the syntax proposal?
The current looks like as someone tries to assign the value to the slice.
for i in a..b {} // [a..b)
for i in a..+b {} //[a..b]
for i in a-..b {} //(a..b)
for i in a-..+b {} // (a..b]
This syntax would make it impossible to add any Add
implementations for ranges in the future. I don't really know if that's a problem though as I'm not really sure what an Add
implementation would do.
Agree for trailing plus. Changed to prefix.
Because a..
, ..b
and ..
are valid constructs in Rust this still produces ambiguities with mathematical operations :/
a...
/ a..=
/ a..+
wouldn't be valid though right? Since having an unbounded range be inclusive of an end point doesn't really make sense.
I wouldn't be too worried about Add
, it doesn't seem like it would be too common of a thing to do with ranges, and if you did want to, you could do (x..+y) + z
?
Edit: never mind, just realized what you meant. It would be a..+b
that'd be ambiguous, right? Since right now it means a.. + b
.
@daboross Not saying this is a good or bad idea, but this implementation (or some variation on it) might be something we want for ranges in the future:
impl<T> Add<T> for RangeFrom<T> where T: Add<T> {
type Output = RangeFrom<T>;
fn add(self, rhs: T) -> RangeFrom<T> {
(self.start + rhs)..
}
}
This specific impl may or may not be a good idea but it didn't take me too long to come up with it as a possible future use case. I think it'd be a bad idea to exclude the possibility of this or something like this being added due to existing syntax decisions.
My other concern is that the whole point of dismissing ...
is to minimize the chance of hard-to recognize typos and allowing parens to make the difference betweeen two different meanings of the abstract a..+b
feels like it would be moving back in that direction.
Furthermore, I have yet to see a solid justification for a-..b
and a-..+b
beyond "wouldn't it be nice for the sake of completeness" while the current ..
and the proposed ..=
both have known, valid rationales.
You cannot use +
for this because it makes expression syntax ambiguous.
EDIT: although, I suppose, it would make implementing inclusive ranges entirely in the library possible, because you’d just end up having an implementation of <Range as Add<{integral}>>::Output = InclusiveRange
.
I may suggest the ..!
variant. It has productive rhyme with ..
and puts additional emphasis on the last dot making it more noticeable.
Of course it would collide with operator !
but, honestly speaking, I could not imagine the situation where one would need to put !
in bound subexpression.
..!
has a problem with intuitiveness. A newcomer not yet familiar with the syntax could easily misunderstand ..!
to mean "up to, but not..."
It also collides with the syntax for "range up to the bitwise complement of..." (Which would put it in a situation similar to using +
)
Agree. But on the other hand ..=
may be misinterpreted as one of assignment operators as well.
What would you be assigning to if this were an assignment operator?
Based on @snuk182's proposal:
a...b // [a; b] shorthand for RangeIncusive
a..-b // [a; b) new Range shorthand
a..b // [a; b) old Range shorthand exists for compatibility
I doubt if we need other combinations. In any case, it can be added later:
a-..b // (a; b]
a-.-b // (a; b)
Although the last option looks a bit weird. ^_^
Please stop proposing any syntax that contains a unary operator i.e. a..-b
, a..!b
, a..*b
, a..&b
or a?..b
, these will never be accepted.
..! has a problem with intuitiveness. A newcomer not yet familiar with the syntax could easily misunderstand ..! to mean "up to, but not..."
..=
has the same problem with intuitiveness. Anyone who see a =
expect an affectation or a comparision. I acknoklege that this syntax complety solve the distinguabilty problem since every time I see ..=
, I can't prevent myself wondering what this equal sign is doing here.
The only proposed syntax that is both pretty intuitive and consistant with current syntax is ...
. But it seems the ship has sailed.
Newcomers will have to look at the docs frequently anyway. Even with ...
they would look at the docs. This is not avoidable. But ..=
has a nice mnemonic (up to
and equal to
) so they won't have to look at the docs for this operator frequently.
What if the ...
operator would remain as it is, but instead we change the ..
operator to ..⎵
, i.e. two dots and a space (I had to put that '⎵' character in there because a regular space wouldn't show on this webpage). It would be the one place in the language where white space matters. It would also be a breaking change, because all code like a..b
would complain that there is no such operator as ..
, and an advise to add at least one space after the dots. I do think that the space makes them visually distinct enough:
a.. b
a...b
@tommit I don't think that would be a good idea to be honest.
The operator is currently quite flexible regarding white spaces, for example: a..b
and a .. b
are the same. This is probably to make it consistent with other operators, such as a+b
and a + b
. These of course, do the same, and allow people to use different syntax styles. That's a good thing if you ask me!
To add to that, the ..=
notation is consistent with the <=
and >=
notations (which are also considered _inclusive_).
It's always good to check what other possibilities there are, but this probably isn't the right solution.
Agree. When compared to <=
and >=
, ..=
looks plausible, even logical.
By the way, if you're a fan of ...
or just dislike how ..=
look, then compromise solution would be to use some font with programming ligatures, like FiraCode with special ligature for ..=
, which may be mapped to ...
, or even something weird, like ⩷, ⩦ or ≔.
Breaking change to all existing rust code using ranges... dead on arrival,
sorry. If someone has a really serious backwards compatible proposal to
address the concerns that have been raised, and it hasn't been discussed
already, let's hear it, but let's at least keep that as the standard, yeah?
This thread is full of bike sheds and the team already made a decision so
that's a high bar to clear.
In any case, there still seems to be lots of bikeshed going on about the actual syntax. I've filed #43086 to stabilize at least the structs and traits and impls so that the core functionality can be used (there seems to be demand, see comment by @retep998 above).
The elephant in the room is that ..
is actually the real problem here because of its symmetric look but asymmetric meaning. The "right thing to do" would probably entail deprecating it, but there is no will to do that because so much code uses it already.
Deprecating the perfectly consistent ...
(symmetric look, symmetric meaning) is the easy way out, but at the cost of adding yet another inconsistent operator in ..=
. It seems like adding bad design to work around previous bad design.
There is another problem with this because of this double inconsistency: There is no good way of adding the last two remaining inclusiveness variants (exclusive on both sides and exclusive on the left side only). Since both ..
and ..=
would already have an implicit =
on the left side, we'd have to negate that somehow, possibly with a <
. So they would have to look something like this:
<..=
for (a; b]
<..
for (a; b)
Good luck guessing correctly what these mean as a newcomer. So they would probably (rightfully) never even be considered to be added.
If ..
wasn't so entrenched or didn't even exist, coming up with a consistent design for all this from scratch doesn't seem so hard, for example:
..
(or ...
) for [a; b]
..<
for [a; b)
<..
for (a; b]
<..<
for (a; b)
I have a feeling that the last two variants might be useful at some point, should we really block the path towards them so quickly? In any case, the only good reason to choose ..=
over deprecating ..
is breaking much less old code, but it would at best be a necessary evil, nothing to be celebrated.
_Edit: Added an example and some comments for more clarity._
I agree with @rkarp on that ..
is the actual problem here, not ...
or ..=
. The asymmetric meaning is especially bad considering that other (more popular) languages actually do assign a symmetric meaning to it. Both Kotlin, Ruby and Haskell consider 5 to be in the range 3..5 for example. Mathematical papers also seem to favor that. The worst thing here is that beginners have no chance of guessing the behavior of 3..5 in Rust: you would either decide that 4 and only 4 is a member of the range 3..5 (iteration over the dots) or that both 3 and 5 are also in it (iterating over 'everything we can see' and the extrapolation of the dots).
I disagree on the difficulty of changing this however. I think @adelarsq suggestion could be implemented relatively painlessly. For reference:
[1..4] // 1, 2, 3, 4
[1..4[ // 1, 2, 3
]1..4] // 2, 3, 4
]1..4[ // 2, 3
Any occurrence of x..y
(without square brackets) could be translated to [x..y[
and emit a compiler warning. After a few releases the compiler could simply refuse to compile "bare" range notations and maybe even offer tools to automatically convert to the new notation.
https://github.com/rust-lang/rust/issues/28237#issuecomment-274216603 that's not a new idea and we can't use it for reasons we've already mentioned.
I absolutely agree with the idea that, if we were designing the language from scratch with the benefit of hindsight, we would want to consider the syntaxes of exclusive and inclusive ranges together. I do think that ..=
is not the ideal operator for an inclusive range; it's simply the best option currently on the table. But deprecating the current exclusive range operator would be excessively painful, and off-putting to existing Rust users and projects.
There are numerous other proposals that could work. For instance, I haven't seen ..@
proposed, and x..@y
does seem evocative. The challenge is to find something more compelling than the current proposal, while retaining the lack of ambiguity.
I wanted to highlight @ssokolow's comment from 2 months back, as my favourite view of the symmetry:
..4
contains things < 4
..=4
contains things <= 4
I think we only need inclusive range, that's more simple.
You can always add + 1 or -1 to change the range.
And .. is better, because .. is simpler than ... !
@AlexanderChen1989 As has been said before in multiple posts on this thread, that's not possible because changing the meaning of ..
would break existing code.
Plus, it's not actually that simple. Most notably, +1
and -1
can cause integer overflow/underflow.)
Here's an example which works in nightly
(via the old candidate for inclusive range syntax) where...
#![feature(inclusive_range_syntax)]
fn main() {
let max = 255u8;
let user_provided = 0u8;
for x in 0...user_provided-1 {
println!("(0 to 0, exclusive via 'inclusive - 1'): {}", x);
}
for x in 0..max+1 {
println!("(0 to 255, inclusive via 'exclusive + 1'): {}", x);
}
}
8 dead on arrival syntax proposals have been made since the language team made their decision. Newcomers please read the thread. We had very good reasons for making the decisions we did I promise.
IMHO the OP should be updated to point out that no more syntax discussion should be had in this thread, and any proposals of exclusive range syntax (i.e. (a, b) and (a, b] ranges) should be moved to another thread.
This should only talk about stuff that needs to be done to stabilise the current plan.
@clarcharr I made a mostly-unilateral decision and added that to the OP, good idea. If the noise continues, I'd vote for locking the thread.
Hi, I'm new and I would like to start working on this one. I will try to write the PR to add ..=
as a synonym for ...
in patterns, deprecate ...
in expressions, and display an error saying that the syntax has been changed to ..=
. Does this sound correct?
@Badel2 cool. I've actually started -- I'll push my branch soon and link it here.
@durka just to clarify -- are you offering to let @Badel2 proceed starting from your branch? Could you mentor them as part of the impl period work? They're in the Gitter channel.
I also agree that the actual problem is ..
. So, the more favorable solution is deprecating (not replacing at once, so it would __not__ break existing code) the ..
in favor of something like ..<
(there will be no confusion between a..<b
and (a..)<b
, since the ..
will eventually cease to exist).
@hyst329 Please read the posts already made.
The very first post says, in boldface text, "No more syntax discussion should be had in this thread."
That aside, what you describe has already been proposed here multiple times by others who didn't read the existing discussion and there are multiple reasons given in response for why it's not a viable option.
Sorry this isn’t very constructive, but every time I see ..=
and try to remember what it is, it looks like an assignment operator similar to +=
or <<=
. It feels very confusing that it is not. (Though admittedly this has been out of context, for example “initial support for ..=
syntax” https://this-week-in-rust.org/blog/2017/10/03/this-week-in-rust-202/)
@SimonSapin I've expressed that same criticism in a more constructive fashion above, suggesting ..^
instead: https://github.com/rust-lang/rust/issues/28237#issuecomment-304325663
For me, one big point that speaks for ..=
is sort-of consistency with swift (which has ..<
for exclusive and ..
for inclusive ranges). This made me be okay with the choice of ..=
.
@SimonSapin I don't think there's anyone who is completely happy with it. The problem is, hundreds of comments have unfortunately confirmed that there isn't a better alternative.
This patch achieved to convince me than doing nothing is better than messing up the current pattern syntax only to cover edge cases.
..
has very few use case in patterns and you can always deal without it.
...
can be replaced by a RangeInclusive
or a (Bound<T>, Bound<T>)
in expressions that really require them. It's more verbose but easy to understand.
@UtherII
...and I was convinced that ...
is untenable by the times I was saved from a subtle bug by the compiler's "You can't use inclusive range syntax on stable" message.
It's just too easy to accidentally hit .
one time too many (or, given the mechanics involved, one time too few) when you're pressing multiple times in rapid succession.
I've checked off the “Change to accept ..=
as synonym for ...
in patterns and to accept ..=
in expressions” because it is already the case after #44709 with the dotdoteq_in_patterns
and inclusive_range_syntax
features respectively. What's remaining is just documentation and stabilization.
I'd like to stabilize inclusive range by 1.26 (beta on March 30th, stable on May 11th) or before. A stabilization PR has been submitted as #47813.
The following 3 features will be stabilized:
inclusive_range_syntax
— The a..=b
and ..=b
syntax in an expression:
for i in 1..=10 {
println!("{:?}", &a[..=i]);
}
dotdoteq_in_patterns
— The a..=b
syntax in a pattern:
match c {
b'0'..=b'9' => c - b'0',
b'a'..=b'z' => c - b'a' + 10,
b'A'..=b'Z' => c - b'A' + 10,
_ => unreachable!(),
}
(Note: the a...b
syntax is still accepted without warnings)
inclusive_range
— The std::ops::{RangeInclusive, RangeInclusiveTo}
types and their fields:
let r = 1..=10;
assert_eq!(r.start, 1);
assert_eq!(r.end, 10);
Several documentation PRs are submitted by @alercah, thanks!
You may find test cases in:
run-pass/range_inclusive.rs
—a..=b
in a for-loop, as an iterator, as a slice index, and precedence of the ..=
operator, and type-checking behavior.libcore/tests/iter.rs
—a..=b
as an iterator; checks that specializations of nth()
, min()
, max()
and last()
work correctly, and that after calling (5..=5).next()
the range becomes 1..=0
.liballoc/tests/str.rs
, liballoc/tests/btree/map.rs
and liballoc/tests/vec.rs
—a..=b
work correctly in various collection types.parse-fail/range_inclusive.rs
and ui/impossible_range.rs
—a..=
and ..=
as an expression cannot be compiledparse-fail/range_inclusive_dotdotdot.rs
—a...b
and ...b
.run-pass/inc-range-pat.rs
—a..=b
and a...b
in a pattern are both accepted and are equivalent.compile-fail/range_traits-1.rs
and compile-fail/range_traits-6.rs
—RangeInclusive
and RangeInclusiveTo
do not implement PartialOrd
(like the other ranges), and RangeInclusive
do not implement Copy
(can't implement Copy since they are iterators). libcore/tests/ops.rs
—RangeInclusive
instance works.Using a..=b
in a for-loop has much lower performance than a..(b+1)
due to its next()
being more complex and less optimizer-friendly. This is currently being tracked in #45222. This is currently mitigated by #48012 when using iterator methods instead of a for-loop.
We might need to keep the fields of RangeInclusive
unstable if we don't want to commit to the design in rust-lang/rfcs#1980 yet.
We might need to keep the fields of RangeInclusive unstable if we don't want to commit to the design in rust-lang/rfcs#1980 yet.
+1
Is there any need to expose range fields directly, rather than through some method-based interface?
@rfcbot fcp merge
Pursuant with @kennytm's summary, I propose that we stabilize the inclusive range syntax (..=
).
(That said, I do find the lower performance of ..=
mildly concerning, and I wonder if it makes sense to make ..=
not directly implemented Iterator
for the time being, in order to mitigate this.)
Team member @nikomatsakis has proposed to merge this. The next step is review by the rest of the tagged teams:
No concerns currently listed.
Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
See this document for info about what commands tagged team members can give me.
I wonder if it makes sense to make ..= not directly implemented Iterator for the time being, in order to mitigate this.
These stable traits are implemented for RangeInclusive<T>
for at least some T
s in current Nightly: Iterator
, FusedIterator
, ExactSizeIterator
, Debug
, Clone
, Eq
, PartialEq
, Hash
, TrustedLen
.
@SimonSapin I forget, did we stabilize the struct already? (In other words, is that observable?) I was assuming it was not.
that said, I also really, really think that 1..2
and 1..=2
should be usable in all the same places, which argues against making any changes here, and instead suggests we should just pursue mitigations and improved optimizations.
Why not special-case ranges where end == MAX
? That could lead to most inclusive ranges having the same code as exclusive ones, with the exception that ones which go directly to the boundary have more code. Because the end != Max
branch would never modify end
, it could easily be inlined by the optimiser.
@nikomatsakis No, the structs are unstable at the moment. But @kennytm’s proposal includes stabilizing them.
@SimonSapin
No, the structs are unstable at the moment. But @kennytm’s proposal includes stabilizing them.
I see; indeed, this is why I mentioned that we would have to remove the Iterator
impl now or never. =)
@SimonSapin I don't think we could stabilize the a..=b
expression syntax without stabilizing the struct. However we could leave the fields unstable, if we do think we may change it.
Right, I didn’t mean to say we shouldn’t stabilize the types when stabilizing the syntax for constructing values of these types.
Huzzah for escaping the range syntax bikeshed and stabilizing! :tada:
Given a time machine, I'd argue for the none of the Range types being :Iterator
, and them just being :IntoIterator
. But as it is now, I think consistency with Range is the best option. We can put this case with Chain
in the "well, it turns out internal iteration is just faster sometimes" bucket.
One other thing that was unresolved, also related to iterating: do we want to commit to any particular representation of the RangeInclusive once done iterating? Right now it ends up at 1..=0
, but there were other options discussed. I don't know if documentation would be sufficient to keep people from relying on it, though the current one does make the post-iteration range quite useless, which at least discourages it.
We can put this case with Chain in the "well, it turns out internal iteration is just faster sometimes" bucket.
I think we really do need to fix it to have no less performance than an exclusive range; there's no fundamental reason for it to be.
there's no _fundamental_ reason for it to be.
It depends on what invariants we can enforce. In the two previous formulations with an extra bit of state, there was nothing enforcing that that bit was actually set when we were done iterating (and in fact it _wasn't_ set on literals like 100..=0
), so the loop exit condition needs to be compound, which means that LLVM is no longer willing to unroll it for us, so we get worse performance. Fully fixing that seems like it means either making the struct bigger and not having pub fields, making RangeInclusive: !Iterator
, or making LLVM better at optimizing these kinds of loops.
That said, I tried some different ways of writing RangeInclusive::next
, and it looks like my past self's choice to match on an Option<Ordering>
is making LLVM very sad--just putting comparison operators there instead is giving much better assembly. Edit PR up: https://github.com/rust-lang/rust/pull/48057
Given a time machine, I'd argue for the none of the Range types being :Iterator, and them just being :IntoIterator. But as it is now, I think consistency with Range is the best option. We can put this case with Chain in the "well, it turns out internal iteration is just faster sometimes" bucket.
I basically agree with this.
:bell: This is now entering its final comment period, as per the review above. :bell:
:bell: This is now entering its final comment period, as per the review above. :bell:
@rfcbot fcp cancel
@withoutboats and @KodrAus have not checked their boxes.
@cramertj proposal cancelled.
@rfcbot fcp merge
https://internals.rust-lang.org/t/psa-tweaks-to-fcp-process/6775
Team member @cramertj has proposed to merge this. The next step is review by the rest of the tagged teams:
No concerns currently listed.
Once a majority of reviewers approve (and none object), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!
See this document for info about what commands tagged team members can give me.
:bell: This is now entering its final comment period, as per the review above. :bell:
@rfcbot fcp concern Range patterns have an operator priority issue - https://github.com/rust-lang/rust/issues/48501.
(@petrochenkov The syntax is @rfcbot concern KEYWORD
, without fcp
. However I think only the tagged team member can register a concern.)
The final comment period is now complete.
If #48501 is enough to block stabilization, I suppose we have to hold off waiting for #48500 to merge (and stabilize)? Ugh, I hope we can find a faster path. This feature has been so long in coming and is now in a precarious state where error messages point people at unstable syntax.
@durka
I don't think this stabilization requires waiting for stabilizing https://github.com/rust-lang/rust/pull/48500.
...
in patterns still exists and it still has the old priority, so if you have &BEGIN ... END
in a pattern (this should be pretty rare I suspect) there's always an option to not change it to ..=
for some time.
I think we can stabilize #48500 pretty quickly
Personally I would be ok w/ landing it "insta-stable"
inclusive range
has been stabilized as #47813 pull request has been merged but it is not in the 1.25 release, why? Though the pull request was merged on 16th March
@mominul a feature is available only after it has been merged into master branch, thus ..=
is available starting from 1.26, not 1.25.
That way you can test them on beta and nightly before they get pushed to stable.
Thanks for the inputs @kennytm and @clarcharr ! Rust 1.26 release is definitely not boring at least for me! :smile:
I know it's too late to pitch in on this discussion, but why not omit the ..
syntax entirely and use words instead?
In Scala you have 1 to 4
which generates [1, 2, 3, 4]
, 1 until 4
which generates [1, 2, 3]
, and an optional by
keyword that follows that indicates step size: 1 to 10 by 2 = [1, 3, 5, 7, 9]
.
This both makes the intent more clear, and avoids the "off-by-one" error that everyone seems so concerned about.
Granted, this does break all the existing code, if the original syntax doesn't stay supported.
@ElectricCoffee in rust ranges support all types though, not just numbers. I think we'd need to introduce to
and until
as keywords in order to support that, and that would be a much bigger breaking change.
It works well in Scala, but Rust's design choices differ largely.
@ElectricCoffee although l would like the feeling of that, I don't think it's desired to add the additional keywords for it.
I believe built-in language keywords should be kept at a minimum, as they might collide with function or variable names.
Although to
and until
aren't words used in the std
(as far as I know), I'm sure they're common words used in to other crates and projects.
@daboross that's actually a fair point that I didn't consider, though that being said, a .. b
does in a sense mean a to b
, regardless of what a
and b
actually are.
And yeah, @timvisee they probably are.
@ElectricCoffee But if to
meant ..=
and until
meant ..
you'd have to write a until b
, not a to b
instead of a .. b
. As you can see, it's not "more" intuitive to use these words than operators.. But it would be more verbose to write until
everywhere that ..
is used, because it's much more common than ..=
(so if words were used, ..
should be assigned the shorter word).
And you're absolutely right about that @Boscop, it was however just an example of how words could be done.
I believe I've seen to
for exclusive and upto
for inclusive in some languages as well.
It was all meant as an idea.
In Scala, the inclusive range is more used than the exclusive one, and thus is given the shorter word.
@timvisee One simply can use a.to(b)
and a.until(b)
, no additional keywords are needed (and it doesn't make the syntax much more clumsy).
@hyst329 I didn't even think about that. I must say, I really like that idea! You are indeed correct.
I don't believe though that this would be a proper full replacement for the current (/new) syntax. But it would be a nice addition.
I have to agree with Boscop's comment about intuitiveness. Aside from words like "including" and "except", day-to-day English doesn't really distinguish between inclusive and exclusive ranges strongly enough for there to be a ready-made shortcut.
Unless it's used in a context where "A through B" is also used, it's ambiguous whether "A to B" means an inclusive or exclusive range in day-to-day speech here in southern Ontario, Canada, and "until" is associated with time strongly enough that, when used in this looser sense, it's unclear whether the "event" that "until" associates with is "until we see X" or "until we've processed X".
@hyst329 Having them as methods would limit ranges to number types, though. I'd really rather not have one syntax for ranges of numbers and another for ranges of other things.
I guess we could add a new catch-all trait to the prelude for creating ranges, but that's still verging on a breaking change for things which have actual to
and until
methods.
I confess, I thought the suggestion of keywords was an April fools joke.
The ..= syntax has been stabilized.
On Thu, Apr 5, 2018 at 1:53 PM, David Ross notifications@github.com wrote:
@hyst329 https://github.com/hyst329 Having them as methods would limit
ranges to number types, though. I'd really rather not have one syntax for
ranges of numbers and another for ranges of other things.I guess we could add a new catch-all trait to the prelude for creating
ranges, but that's still verging on a breaking change for things which have
actual to and until methods.—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/rust-lang/rust/issues/28237#issuecomment-379021814,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAC3nwBj-b985q1Ez0OHDEHkG6DWV_5nks5tlloZgaJpZM4F4LbW
.
Yeah, we've had 300+ comments on this issue, and the very first comment says:
no more syntax discussion should be had in this thread. Any different proposals of exclusive range syntax should take place on the user's forum or internals forum, after you have read all existing comments and their rationale here. Notably, breaking backwards compatibility is a non-starter.
I'm going to lock this issue for now, sorry if I'm stepping on any toes!
I believe that everything covered by this issue is done. Please reopen and update the checklist in the issue’s original message if there’s something else.
Most helpful comment
The lang team discussed this feature again in our meeting today, and arrived at roughly the following matrix:
Supporting
...
in patterns and..
in expressions, and nothing else, is an untenable design; it leads users to expect syntax to work that won't.Allowing
...
and..
in both places would help, but the off-by-one problem is a real concern; none of us are eager to receive reports of people spending hours tracking down an errant period.Moving to
..=
and..
is less aesthetically appealing, but avoids the very real practical problems above. We can also roll it out in a very gentle fashion: first introduce..=
as an alternative syntax (which rustfmt can rewrite to), and only after it has become idiomatic, deprecate...
.In case you can't tell, the team feels that
..=
and..
is our best path forward. This issue has also been discussed to the point that it's very unlikely that new arguments will be raised, so we're ready to take the plunge: we're looking for someone who would like to implement..=
notation for both patterns and expressions, and subsequently we'll go to FCP!