Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upUndisambiguated generic arguments in expressions #2527
Conversation
This comment was marked as resolved.
Show comment
Hide comment
This comment was marked as resolved.
Centril
Aug 20, 2018
Some linebreaks would be real nice here (and elsewhere), whether at 80 characters or http://rhodesmill.org/brandon/2012/one-sentence-per-line/ :P
Centril
commented on text/0000-undisambiguated-expr-generics.md in da0b0ce
Aug 20, 2018
•
|
Some linebreaks would be real nice here (and elsewhere), whether at 80 characters or http://rhodesmill.org/brandon/2012/one-sentence-per-line/ :P |
This comment was marked as resolved.
Show comment
Hide comment
This comment was marked as resolved.
|
As soon as I start editing Markdown, I forget all about line breaks |
This comment was marked as resolved.
Show comment
Hide comment
This comment was marked as resolved.
Centril
commented on text/0000-undisambiguated-expr-generics.md in da0b0ce
Aug 20, 2018
|
Some link as to why this step was taken would be nice here. |
This comment was marked as resolved.
Show comment
Hide comment
This comment was marked as resolved.
Centril
commented on text/0000-undisambiguated-expr-generics.md in da0b0ce
Aug 20, 2018
|
s/In order to/to/ |
This comment was marked as resolved.
Show comment
Hide comment
This comment was marked as resolved.
Centril
commented on text/0000-undisambiguated-expr-generics.md in da0b0ce
Aug 20, 2018
|
s/Obviously this/This/ |
varkor
added some commits
Aug 20, 2018
Centril
added
the
T-lang
label
Aug 21, 2018
varkor
referenced this pull request
Aug 21, 2018
Closed
[Do not merge] Allow generic parameters to be specified in expressions without `::` #53511
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
xfix
Aug 21, 2018
Contributor
What about statements like this?
let a = (b<c, d>(e));This can either mean a tuple
let a = ((b < c), (d > e));Or a pair of generic arguments.
let a = b::<c, d>(e);|
What about statements like this? let a = (b<c, d>(e));This can either mean a tuple let a = ((b < c), (d > e));Or a pair of generic arguments. let a = b::<c, d>(e); |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
varkor
Aug 21, 2018
Contributor
@xfix: it seems we had overlooked that ambiguity before, thanks for pointing it out. This looks pretty difficult to resolve to me, so I'm going to close the RFC unless someone has a resolution. It was a nice idea while it lasted
|
@xfix: it seems we had overlooked that ambiguity before, thanks for pointing it out. This looks pretty difficult to resolve to me, so I'm going to close the RFC unless someone has a resolution. It was a nice idea while it lasted |
varkor
closed this
Aug 21, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
This was a sad day! |
varkor
referenced this pull request
Aug 21, 2018
Merged
Lament the invincibility of the Turbofish #53562
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
varkor
Aug 21, 2018
Contributor
I've opened rust-lang/rust#53562 to ensure that any such attempt to unroot the Turbofish in the future shall have an earlier warning than we did.
|
I've opened rust-lang/rust#53562 to ensure that any such attempt to unroot the Turbofish in the future shall have an earlier warning than we did. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
novacrazy
Aug 21, 2018
Additionally, at least for me personally, turbofish is less ambiguous for mentally parsing code as well, so I’ve never minded the slightly more verbose syntax. I also really don’t like this recent trend of oversimplification in Rust just to save a few characters...
novacrazy
commented
Aug 21, 2018
|
Additionally, at least for me personally, turbofish is less ambiguous for mentally parsing code as well, so I’ve never minded the slightly more verbose syntax. I also really don’t like this recent trend of oversimplification in Rust just to save a few characters... |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
varkor
Aug 21, 2018
Contributor
@novacrazy: I think you'll find that there's always been a trend of reducing characters in Rust (take a look at all the keywords)
Regardless, the motivation here wasn't to reduce characters, but to make the syntax more consistent. I think you never get a syntax everyone agrees on, but in the case of turbofish, most of the feedback I've gathered has been positive towards making it unnecessary if possible.
It's a moot point, though.
|
@novacrazy: I think you'll find that there's always been a trend of reducing characters in Rust (take a look at all the keywords) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
|
@varkor Have you considered allowing only |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Havvy
Aug 21, 2018
Contributor
@eddyb I feel like allowing it in the special case of zero or one type parameters is going to cause confusion and make the grammar that much more irregular.
|
@eddyb I feel like allowing it in the special case of zero or one type parameters is going to cause confusion and make the grammar that much more irregular. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
BatmanAoD
Aug 22, 2018
I'm actually with @novacrazy on this: I think the Rust syntax is superior to the C++ syntax. Angle brackets are pretty common; disambiguation is helpful.
BatmanAoD
commented
Aug 22, 2018
|
I'm actually with @novacrazy on this: I think the Rust syntax is superior to the C++ syntax. Angle brackets are pretty common; disambiguation is helpful. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Laaas
Aug 22, 2018
This is really unfortunate, but I'd still really like a unification of providing generic arguments. The simplest way I see would just be to require turbofish everywhere, but this is quite ugly.
Laaas
commented
Aug 22, 2018
|
This is really unfortunate, but I'd still really like a unification of providing generic arguments. The simplest way I see would just be to require turbofish everywhere, but this is quite ugly. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
artemshein
Aug 22, 2018
I like the approach Kotlin takes on the disambiguation -- in case it's not obvious how to parse an expression, Kotlin will ask to put parens. No turbofish and no additional parens 99% of the time.
artemshein
commented
Aug 22, 2018
|
I like the approach Kotlin takes on the disambiguation -- in case it's not obvious how to parse an expression, Kotlin will ask to put parens. No turbofish and no additional parens 99% of the time. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
comex
Aug 22, 2018
Hmm… interesting. Kotlin doesn't have tuples (or a comma operator like C++), but you can get a similar effect with function calls:
foo(b<c, d>(e))
This appears to always be parsed as a generic, not a pair of comparisons – see this test program.
I suppose that if you did want two comparisons, you could add parentheses:
foo((b<c), d>(e))
^ ^
The approach Kotlin uses seems to require infinite lookahead. When encountering a <, the parser can't commit to whether to parse it as a comparison or as the start of a generic parameter list. For example, if the parser has seen this fragment of code so far:
foo(b<c,b<c,b<c,b<c,b<c,b<c,b<c,
…these are all legal ways for the line to end:
foo(b<c,b<c,b<c,b<c,b<c,b<c,b<c, 4)(all comparisons)foo(b<c,b<c,b<c,b<c,b<c,b<c,b<c, Int>>>>>>>())(all generics)foo(b<c,b<c,b<c,b<c,b<c,b<c,b<c, Int>>>())(a mix of comparisons and generics!!)
(To play with this, look at lookahead.kt in the last link.)
That leaves me with a few questions:
-
Are there peculiarities in Rust's grammar that would make adopting the same approach difficult or impossible?
-
Was this approach ever considered in Rust's olden days, and rejected due to infinite lookahead or some other downside? (IMO, the benefit of killing turbofish would be well worth it.) Or did nobody realize it was a possibility?
-
Adopting this in Rust would necessarily be a breaking change. Given
(a<b, c>(d)), the 'default' parse must be as a generic rather than a comparison – because the comparison can be disambiguated by adding parentheses (((a<b), c>(d))) but there's nowhere to add parentheses that would make a generic the only valid interpretation. However, Rust currently does the opposite, defaulting to a comparison and requiring turbofish to force interpretation as a generic.Unfortunately, even if everyone suddenly agreed Rust should switch, AFAIK it would be way too late for the Rust 2018 edition. So… when's the next edition? :)
comex
commented
Aug 22, 2018
|
Hmm… interesting. Kotlin doesn't have tuples (or a comma operator like C++), but you can get a similar effect with function calls:
This appears to always be parsed as a generic, not a pair of comparisons – see this test program. I suppose that if you did want two comparisons, you could add parentheses:
The approach Kotlin uses seems to require infinite lookahead. When encountering a
…these are all legal ways for the line to end:
(To play with this, look at That leaves me with a few questions:
|
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
BatmanAoD
Aug 22, 2018
The advantage of ::< is that it looks obviously generics-related. This seems like a clear win for readability, especially compared to the disambiguate-with-parens strategy.
BatmanAoD
commented
Aug 22, 2018
|
The advantage of |
added a commit
to GuillaumeGomez/rust
that referenced
this pull request
Aug 22, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
I'm betting on 2021 :) |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
artemshein
commented
Aug 22, 2018
|
For me |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
BatmanAoD
Aug 22, 2018
@artemshein I think that makes some sense, though. MyGeneric::<args...> is selecting the <args...> variant from the set of possible functions or types provided by MyGeneric. "Select x from the set X" is also how :: behaves when used on namespaces: X::x.
BatmanAoD
commented
Aug 22, 2018
•
|
@artemshein I think that makes some sense, though. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
artemshein
Aug 22, 2018
@BatmanAoD haha, ok, maybe, but it's still ugly and doesn't reflect the generic parameters definition syntax (Option::<T>, anyone?) and introduces one more unnecessary thing to learn (big enough to have a full page of explanations like this https://matematikaadit.github.io/posts/rust-turbofish.html).
artemshein
commented
Aug 22, 2018
|
@BatmanAoD haha, ok, maybe, but it's still ugly and doesn't reflect the generic parameters definition syntax ( |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
BatmanAoD
Aug 22, 2018
@artemshein The "ugliness" is of course a matter of personal aesthetic preference. Personally, coming from C++, when I saw the turbofish I immediately thought "oh, great, that's an improvement!" I realize I am in the minority in this view, though.
If consistency is the goal, I would personally rather see definition syntax match expression context, i.e. pub enum Option::<T>. I haven't proposed this because I assume that there are very few people who share that aesthetic preference and that the proposal would therefore immediately be shot down.
I also wouldn't call it an "unnecessary thing to learn." Clearly, per this discussion, it is necessary for Rust, even if it's different from C++!
BatmanAoD
commented
Aug 22, 2018
|
@artemshein The "ugliness" is of course a matter of personal aesthetic preference. Personally, coming from C++, when I saw the turbofish I immediately thought "oh, great, that's an improvement!" I realize I am in the minority in this view, though. If consistency is the goal, I would personally rather see definition syntax match expression context, i.e. I also wouldn't call it an "unnecessary thing to learn." Clearly, per this discussion, it is necessary for Rust, even if it's different from C++! |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
Laaas
Aug 22, 2018
Yeah I think just using turbofish everywhere might be superior. One idea I also had was since people dislike writing ::<>, change the :: part to !. Then it would be transmute!<f32>(x) and Option!<Vec!<u32>>. You could argue that it would be confused with macros, but is it really so wrong to consider generic items to be a macro of sorts, that generate a variant from the arguments? Though the change this would require might not make it worth it to switch away from the turbofish.
Laaas
commented
Aug 22, 2018
•
|
Yeah I think just using turbofish everywhere might be superior. One idea I also had was since people dislike writing |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
BatmanAoD
Aug 22, 2018
@Laaas I had the ! thought last night as well. I've seen it used with essentially the correct semantics (except with the reverse order) in non-programming contexts, e.g. referring to the version of Harry Potter from HPMOR as "Rational!Harry". So I think for some people, ! would be a natural way to express the concept.
BatmanAoD
commented
Aug 22, 2018
|
@Laaas I had the |
added a commit
to Mark-Simulacrum/rust
that referenced
this pull request
Aug 22, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
petrochenkov
Aug 23, 2018
Contributor
chained comparisons are now banned in Rust
I assumed that they are banned now so they can be allowed in the future in their intuitive meaning - a < b < c == a < b && b < c.
In this case we cannot reuse the syntax for generic arguments.
backtracking is already present for some (uncommon) cases in the Rust parser
All these cases exist purely for diagnostics and can be removed without affecting Rust grammar.
This feature also directly interferes with const generic parameters.
When they are implemented, even the most innocent stuff like x < 10 && y > 10 will start looking like generic arguments.
I assumed that they are banned now so they can be allowed in the future in their intuitive meaning -
All these cases exist purely for diagnostics and can be removed without affecting Rust grammar. This feature also directly interferes with const generic parameters. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
varkor
Aug 23, 2018
Contributor
I assumed that they are banned now so they can be allowed in the future in their intuitive meaning
- a < b < c==a < b && b < c.
In this case we cannot reuse the syntax for generic arguments.
That was one of the motivations, but note that chained comparisons in the same direction (e.g. a < b < c) are unambiguous with generics. The syntax that is not is a < b > c: I think a strong case could (and would) be made against this syntax for chained comparisons as it is far less readable than a < b && c < b (or equivalent). So I don't think this conflicts with generic arguments.
@petrochenkov:
Is this backtracking for diagnostic use only:
https://github.com/rust-lang/rust/blob/e9e7e53c31a3d1169ac81f0e82ca9168167ce711/src/libsyntax/parse/parser.rs#L3136-L3145
I couldn't tell from a quick look, but it looked plausibly like it could be used for non-error situations.
This feature also directly interferes with const generic parameters.
When they are implemented, even the most innocent stuff likex < 10 && y > 10will start looking like generic arguments.
Expressions that are const generic arguments must be enclosed within a block, therefore no additional ambiguity over the tuple case given above is created.
That was one of the motivations, but note that chained comparisons in the same direction (e.g. @petrochenkov:
Expressions that are const generic arguments must be enclosed within a block, therefore no additional ambiguity over the tuple case given above is created. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
petrochenkov
Aug 23, 2018
Contributor
Is this backtracking for diagnostic use only
Yes, if self.parse_ty_no_plus() (which is the only interpretation according to the grammar) fails, then we return an error on every path.
Expressions that are const generic arguments must be enclosed within a block, therefore no additional ambiguity over the tuple case given above is created.
Text and examples in that RFC contradict to itself, the precise rules were written out somewhere in the discussion thread, but were never moved into the RFC text properly.
The important part is that anything starting with a literal is certainly an expression and is unambiguously parsed as a const argument, so things like Type<1, 1 + 2> work without requiring braces.
Yes, if
Text and examples in that RFC contradict to itself, the precise rules were written out somewhere in the discussion thread, but were never moved into the RFC text properly. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
comex
Aug 23, 2018
Well, x < 10 && y > 10 wouldn’t be ambiguous because 10 can’t appear after a type or expression.
However, x < 10 && y > -10 would be...
…that is, under the parsing rule for const parameters that you describe. But it’s worth noting that const generics are not stable (or even fully implemented) yet, so it would be possible to change the rule back to what’s described in the RFC. It’s certainly nice to minimize the need for braces in const parameters, but on the other hand, “expressions that start with literals but reference variables later on” seems like a relatively narrow category.
comex
commented
Aug 23, 2018
|
Well, However, …that is, under the parsing rule for const parameters that you describe. But it’s worth noting that const generics are not stable (or even fully implemented) yet, so it would be possible to change the rule back to what’s described in the RFC. It’s certainly nice to minimize the need for braces in const parameters, but on the other hand, “expressions that start with literals but reference variables later on” seems like a relatively narrow category. |
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
comex
Aug 23, 2018
...that said, if x<10 && y> - 10 (i.e. the const generics interpretation) would otherwise be valid syntax, and would thus conflict with x < 10 && y > -10, the grammar could and should still resolve the ambiguity in favor of the latter. That’s because in this case, both alternatives have a place to add parentheses to force them: you could force the first interpretation by writing x<{10 && y}> - 10, or force the second with (x < 10) && (y > -10). Thus, either option would be an acceptable default, in the sense of not making anything impossible to express.
Since it’s rather unlikely that 10 && y would typecheck (albeit not impossible), it would clearly be better to default to the other interpretation.
comex
commented
Aug 23, 2018
•
|
...that said, if Since it’s rather unlikely that |
added a commit
to kennytm/rust
that referenced
this pull request
Aug 24, 2018
This comment has been minimized.
Show comment
Hide comment
This comment has been minimized.
estebank
Sep 13, 2018
Contributor
Is this backtracking for diagnostic use only:
https://github.com/rust-lang/rust/blob/e9e7e53c31a3d1169ac81f0e82ca9168167ce711/src/libsyntax/parse/parser.rs#L3136-L3145
I couldn't tell from a quick look, but it looked plausibly like it could be used for non-error situations
@varkor If you look at all 3 possible branches after that, the parser will always emit an error, either the original error or the specific error suggesting to use parenthesis around the expression. That code path could be removed and the compiler would start accepting that code as valid (which is what rust-lang/rust#42578 originally did), but the agreement was reached that it was a bad idea to change the grammar in that way.
I would love it if with the code you wrote in rust-lang/rust#53578, we could add the same kind of suggestions for people that may not have the full grasp of how to use the turbofish yet. We would not be fixing the ergonomics issue, but we'd be fixing part of the discoverability problem.
@varkor If you look at all 3 possible branches after that, the parser will always emit an error, either the original error or the specific error suggesting to use parenthesis around the expression. That code path could be removed and the compiler would start accepting that code as valid (which is what rust-lang/rust#42578 originally did), but the agreement was reached that it was a bad idea to change the grammar in that way. I would love it if with the code you wrote in rust-lang/rust#53578, we could add the same kind of suggestions for people that may not have the full grasp of how to use the turbofish yet. We would not be fixing the ergonomics issue, but we'd be fixing part of the discoverability problem. |
varkor commentedAug 21, 2018
•
edited
Make disambiguating generic arguments in expressions with
::optional, allowing generic arguments to be specified without::(making the "turbofish" notation no longer necessary).Rendered
This makes the following valid syntax:
Thanks to @Centril, @scottmcm, @joshtriplett and @rpjohnst for feedback!