Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upsyntax: Lower priority of `+` in `impl Trait`/`dyn Trait` #45294
Conversation
rust-highfive
assigned
nikomatsakis
Oct 15, 2017
kennytm
added
S-waiting-on-review
T-lang
labels
Oct 15, 2017
This comment has been minimized.
This comment has been minimized.
|
While I understand the idea of trying to be as flexible as possible, I do wonder if it breaks the principle of least surprise to accept non-parenthesized Put another way: will users be more likely to write the correct thing for case of |
This comment has been minimized.
This comment has been minimized.
|
I'm not sure yet what I think about (On the other hand, since |
This comment has been minimized.
This comment has been minimized.
I had a similar reaction. (I also wonder if this is something we might consider tweaking in a Rust 2.0 Epoch -- i.e., we could encourage Rust 1.x users to write |
nikomatsakis
added
the
I-nominated
label
Oct 17, 2017
This comment has been minimized.
This comment has been minimized.
There's certainly a trade-off here between "least surprise" and "least annoyance". |
This comment has been minimized.
This comment has been minimized.
Why? The
This is certainly a viable alternative, maybe even more intuitive, even if it requires writing strictly more parens. Unnecessary parentheses for all sums are still pretty bad though. If I weren't followed the language closely and suddenly discovered that I need to change my |
This comment has been minimized.
This comment has been minimized.
|
Oh, also In #45175 it is disambiguated in favor of a function-like trait |
This comment has been minimized.
This comment has been minimized.
If |
This comment has been minimized.
This comment has been minimized.
|
Rather than changing the precedence, personally, if we decided to change this I'd just as soon make it non-associative, requiring you to write parentheses for either meaning. Because I think |
This comment has been minimized.
This comment has been minimized.
|
@joshtriplett x as &Trait + y
x as &dyn Trait + yThis is currently parsed as type A = &A + B; // ERROR
type B = &dyn A + B; // OKEven if it's technically parseable and explainable, the inconsistency and the only precedent of binary "operator" having higher precedence than unary ones still annoy me. |
This comment has been minimized.
This comment has been minimized.
|
Parsing |
carols10cents
added
S-waiting-on-team
and removed
S-waiting-on-review
labels
Oct 23, 2017
This comment has been minimized.
This comment has been minimized.
|
Maybe I should write a mini-RFC describing possible alternatives, to get some wider feedback? |
This comment has been minimized.
This comment has been minimized.
|
@petrochenkov sorry, I've been meaning to reply to this PR for a while now. I'd be in favor of a mini-RFC, or at least a good write-up. What I really want is an official rust grammar to express these changes in terms of. I'm sad we don't have that yet, though I think there are some unofficial ones that have progress quite far. Let me at least clarify how I expected it to work. My expectation was that we had a setup like this:
This is kinda' what we have now, or at least how I think of it, and it means that I can do (e.g.) However, I realize that this doesn't account for bounds that are not types, like |
This comment has been minimized.
This comment has been minimized.
OK, I see. You mean that right now |
This comment has been minimized.
This comment has been minimized.
|
I'll write an RFC. |
petrochenkov
closed this
Nov 10, 2017
This comment has been minimized.
This comment has been minimized.
|
@petrochenkov are you still planning to write something up for this? |
This comment has been minimized.
This comment has been minimized.
|
@cramertj |
petrochenkov
referenced this pull request
Dec 16, 2017
Merged
mini-RFC: Finalize syntax of `impl Trait` and `dyn Trait` with multiple bounds #2250
This comment has been minimized.
This comment has been minimized.
|
RFC is submitted |
This comment has been minimized.
This comment has been minimized.
|
@petrochenkov I'm trying to get |
This comment has been minimized.
This comment has been minimized.
|
@cramertj |
petrochenkov
reopened this
Jan 18, 2018
petrochenkov
force-pushed the
petrochenkov:prioplus
branch
2 times, most recently
from
2ef5fe8
to
732a623
Jan 18, 2018
petrochenkov
removed
I-nominated
S-waiting-on-team
labels
Jan 18, 2018
This comment has been minimized.
This comment has been minimized.
|
Rebased. |
nikomatsakis
reviewed
Jan 19, 2018
| type A = fn() -> A + B; | ||
| //~^ ERROR expected a path on the left-hand side of `+`, not `fn() -> A` | ||
|
|
||
| type A = Fn() -> impl A + B; // OK, interpreted as `(Fn() -> impl A) + B` |
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jan 19, 2018
Contributor
Can we get a separate test showing that this errors? (Hmm, well, I suppose such a test already exists, right @cramertj ?)
nikomatsakis
reviewed
Jan 19, 2018
| //~^ ERROR expected a path on the left-hand side of `+`, not `fn() -> A` | ||
|
|
||
| type A = Fn() -> impl A + B; // OK, interpreted as `(Fn() -> impl A) + B` | ||
| type A = Fn() -> dyn A + B; // OK, interpreted as `(Fn() -> dyn A) + B` |
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jan 19, 2018
Contributor
But this case probably doesn't error -- I sort of think it should.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jan 19, 2018
Contributor
I suppose we might be able to target it by linting. I did think though there was some desire to "put off" the question of just how Fn and friends works when combined with dyn and impl.
This comment has been minimized.
This comment has been minimized.
cramertj
Jan 19, 2018
Member
+1-- I think users should have to manually specify parentheses here, at least for the time-being.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jan 19, 2018
Contributor
It'll be a bit of a pain to do, I guess, since saying dyn A + B and A + B have same priority means that this case just kind of "falls out" this way.
But I still feel like we should keep future flexibility here.
@petrochenkov what do you think?
This comment has been minimized.
This comment has been minimized.
petrochenkov
Jan 20, 2018
•
Author
Contributor
Clippy lint?
To put this into perspective - few people remember priorities of operators in expressions beyond * vs + and && vs ||, but we still don't require parentheses everywhere.
Here if you get the priorities of Fn and + wrong, you won't even get runtime bugs, like with expressions, you'll get a type checking error about unsatisfied bounds, giving you opportunity to learn what priority is right.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jan 23, 2018
Contributor
More directly, I think the point was that we know we want this to work:
fn foo() -> impl A + B { }and that therefore one might very well expect this to work:
where F: Fn() -> impl A + BNow, historically, as @petrochenkov correctly points out, we opted not to make the where clause case "work" (that is, where F: Fn() -> A + B parses two bounds on F) because returning a value of type A + B was almost certainly not what you wanted. However, introducing keywords like dyn and impl gives us a chance to revisit this decision backwards compatibly, and it is not clear that -- in this new context -- this is the correct behavior.
As @cramertj noted, I don't think that anyone feels the correct interpretation of where F: Fn() -> impl A + B would be that B is a bound on F. (It's just a bit too surprising.) And moreover it seems clear that dyn ought to behave the same as impl. However, there were also those -- notably @joshtriplett -- who felt that there is no correct interpretation, and we should just avoid guessing. (I think that @joshtriplett would also be happy to have required parentheses in other cases where we do not today, e.g. amongst confusing operators.)
Given this, it does seem like we ought to try to disallow -> impl A + B and -> dyn A + B for now.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
Jan 23, 2018
Contributor
Let me try to clarify what I think we want. We already have two grammatical precedence levels for types. I'll them T0 and T1. T0 includes all types, including the "n-ary" operators like dyn A + B and A + B. T1 excludes those n-ary operators, and is used primarily for & types (which take a T1 argument).
Currently, in the parser, if we are parsing a T1 type and we see a + we stop, leaving it to be consumed from some enclosing context.
I think that we want to have a rule such that T1 = dyn Identifier parses, but if we see a + after that, we error out (in the parser). This means that &dyn A + B is a parse error, as is where F: Fn() -> dyn A + B, and (I imagine) dyn Fn() -> dyn A + B.
I think of this as being analogous to non-associativity: there are basically multiple interpretations of the + operator. It can be adding bounds to a type parameter in a where-clause list; it can be adding types in a (old-style, A+B) sum-type; and it can be adding bounds to a dyn or impl type.
In a fn item context, or in the T in Box<T>, once we see dyn or impl, there is only one valid interpretation (since we are not in a where clause list) -- . These are exactly the cases (I believe) where we use the grammatical nonterminal T0. We can parse eagerly there.
In other contexts, there are multiple contending interpretations. We are aiming not to choose between them (hence "non-associative").
This comment has been minimized.
This comment has been minimized.
petrochenkov
Jan 23, 2018
•
Author
Contributor
@nikomatsakis
Thanks for the detailed write up, this is better than just "lang team decided so".
I agree that consistency with fn f() -> A + B { ... } is a good argument in favor of potentially swapping priorities for Fn and + in the future.
I'll implement the conservative "refuse to disambiguate" solution.
This comment has been minimized.
This comment has been minimized.
petrochenkov
Jan 23, 2018
Author
Contributor
This discussion should've probably been happening in rust-lang/rfcs#2250, because the RFC needs to be updated as well.
This comment has been minimized.
This comment has been minimized.
nikomatsakis
reviewed
Jan 19, 2018
|
|
||
| type A = Fn() -> impl A + B; // OK, interpreted as `(Fn() -> impl A) + B` | ||
| type A = Fn() -> dyn A + B; // OK, interpreted as `(Fn() -> dyn A) + B` | ||
| type A = Fn() -> A + B; // OK, interpreted as `(Fn() -> A) + B` |
This comment has been minimized.
This comment has been minimized.
petrochenkov
added
S-waiting-on-author
and removed
S-waiting-on-review
labels
Jan 25, 2018
petrochenkov
added some commits
Oct 14, 2017
petrochenkov
force-pushed the
petrochenkov:prioplus
branch
from
732a623
to
f57ea7c
Jan 27, 2018
This comment has been minimized.
This comment has been minimized.
|
Updated. |
petrochenkov
added
S-waiting-on-review
and removed
S-waiting-on-author
labels
Jan 27, 2018
nikomatsakis
approved these changes
Jan 29, 2018
This comment has been minimized.
This comment has been minimized.
|
@bors r+ Looks nice! |
This comment has been minimized.
This comment has been minimized.
|
|
nikomatsakis
added
S-waiting-on-bors
and removed
S-waiting-on-review
labels
Jan 29, 2018
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
Jan 29, 2018
This comment has been minimized.
This comment has been minimized.
|
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
bors
added a commit
that referenced
this pull request
Jan 30, 2018
This comment has been minimized.
This comment has been minimized.
|
|
petrochenkov commentedOct 15, 2017
•
edited
Now you have to write
Fn() -> (impl A + B)instead ofFn() -> impl A + B, this is consistent with priority of+in trait objects (Fn() -> A + Bmeans(Fn() -> A) + B).To make this viable I changed the syntax to also permit
+in return types in function declarationsbut you still have to use
-> (dyn A + B)in function types and function-like trait object types (see this PR's tests for examples).This can be a breaking change for code using
impl Traiton nightly. The thing that is most likely to break is&impl A + B, it needs to be rewritten as&(impl A + B).cc #34511 #44662 rust-lang/rfcs#438