Uh oh!
There was an error while loading. Please reload this page.
syntax: Lower priority of + in impl Trait/dyn Trait - #45294
Conversation
pnkfelix
commented
Oct 16, 2017
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 |
nikomatsakis
commented
Oct 16, 2017
I'm not sure yet what I think about (On the other hand, since |
nikomatsakis
commented
Oct 16, 2017
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 |
petrochenkov
commented
Oct 17, 2017
There's certainly a trade-off here between "least surprise" and "least annoyance". |
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 |
Oh, also In #45175 it is disambiguated in favor of a function-like trait |
If |
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 |
petrochenkov
commented
Oct 19, 2017
@joshtriplett x as&Trait + y
x as&dynTrait + yThis is currently parsed as typeA = &A + B;// ERRORtypeB = &dynA + 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. |
joshtriplett
commented
Oct 19, 2017
Parsing |
petrochenkov
commented
Nov 4, 2017
Maybe I should write a mini-RFC describing possible alternatives, to get some wider feedback? |
@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 |
OK, I see. You mean that right now |
petrochenkov
commented
Nov 10, 2017
I'll write an RFC. |
@petrochenkov are you still planning to write something up for this? |
petrochenkov
commented
Nov 28, 2017
@cramertj |
petrochenkov
commented
Dec 16, 2017
RFC is submitted |
cramertj
commented
Jan 17, 2018
@petrochenkov I'm trying to get |
petrochenkov
commented
Jan 17, 2018
@cramertj |
2ef5fe8 to
732a623CompareThere was a problem hiding this comment.
Can we get a separate test showing that this errors? (Hmm, well, I suppose such a test already exists, right @cramertj ?)
There was a problem hiding this comment.
But this case probably doesn't error -- I sort of think it should.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
+1-- I think users should have to manually specify parentheses here, at least for the time-being.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
More directly, I think the point was that we know we want this to work:
fnfoo() -> implA + B{}and that therefore one might very well expect this to work:
whereF:Fn() -> implA + 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.
There was a problem hiding this comment.
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").
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
This discussion should've probably been happening in rust-lang/rfcs#2250, because the RFC needs to be updated as well.
There was a problem hiding this comment.
Agreed, I'll copy my post over there.
There was a problem hiding this comment.
Obviously we can't alter this though.
`+` is still disallowed in function types and function-like traits
petrochenkov
commented
Jan 27, 2018
Updated. |
nikomatsakis
commented
Jan 29, 2018
@bors r+ Looks nice! |
bors
commented
Jan 29, 2018
📌 Commit f57ea7c has been approved by |
bors
commented
Jan 29, 2018
syntax: Lower priority of `+` in `impl Trait`/`dyn Trait`
Now you have to write `Fn() -> (impl A + B)` instead of `Fn() -> impl A + B`, this is consistent with priority of `+` in trait objects (`Fn() -> A + B` means `(Fn() -> A) + B`).
To make this viable I changed the syntax to also permit `+` in return types in function declarations
```
fn f() -> dyn A + B { ... } // OK, don't have to write `-> (dyn A + B)`
// This is acceptable, because `dyn A + B` here is an isolated type and
// not part of a larger type with various operator priorities in play
// like `dyn A + B` in `Fn() -> dyn A + B` despite syntax similarities.
```
but 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 Trait` on nightly. The thing that is most likely to break is `&impl A + B`, it needs to be rewritten as `&(impl A + B)`.
cc #34511#44662rust-lang/rfcs#438bors
commented
Jan 30, 2018
💔 Test failed - status-appveyor |
kennytm
commented
Jan 30, 2018
bors
commented
Jan 30, 2018
syntax: Lower priority of `+` in `impl Trait`/`dyn Trait`
Now you have to write `Fn() -> (impl A + B)` instead of `Fn() -> impl A + B`, this is consistent with priority of `+` in trait objects (`Fn() -> A + B` means `(Fn() -> A) + B`).
To make this viable I changed the syntax to also permit `+` in return types in function declarations
```
fn f() -> dyn A + B { ... } // OK, don't have to write `-> (dyn A + B)`
// This is acceptable, because `dyn A + B` here is an isolated type and
// not part of a larger type with various operator priorities in play
// like `dyn A + B` in `Fn() -> dyn A + B` despite syntax similarities.
```
but 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 Trait` on nightly. The thing that is most likely to break is `&impl A + B`, it needs to be rewritten as `&(impl A + B)`.
cc #34511#44662rust-lang/rfcs#438bors
commented
Jan 30, 2018
☀️ Test successful - status-appveyor, status-travis |
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#44662rust-lang/rfcs#438