-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Associated type bounds of form MyTrait<AssociatedType: Bounds>
#2289
Conversation
MyTrait<AssociatedType: Bounds>
text/0000-associated-type-bounds.md
Outdated
# Unresolved questions | ||
[unresolved]: #unresolved-questions | ||
|
||
- Does this introduce any parsing ambiguities? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were pretty sure that there wouldn't be any - but it is nice to get that confirmed.
I'll remove this question in a bit then =)
text/0000-associated-type-bounds.md
Outdated
[reference-level-explanation]: #reference-level-explanation | ||
|
||
The surface syntax `Trait<AssociatedType: Bounds>` should desugar to | ||
`Trait<AssociatedType = impl Bounds>` anywhere it appears. This syntax |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure literally desugaring like this will be legal and will mean the same thing in all contexts.
Maybe it's better to use the desugaring from the examples above (T: Trait<AssocTy: Bounds>
-> T: Trait + <T as Trait>::AssocTy: Bounds
) everywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice sugar, overall.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So in other words: T: Trait<AssocTy: Bounds>
desugars into adding <T as Trait>::AssocTy: Bounds
to the list of where
clauses? How do we deal with that in the case of the following?
fn printables() -> impl Iterator<Item: Display> {
// ..
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Centril -> impl Iterator<Item = T>
is already desugared to two bounds, X: Iterator
and <X as Iterator>::Item == T
, where X
is the impl Iterator<Item = T>
nominal type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@petrochenkov That's great! It means we can implement this today with very little effort, since we have all the needed infrastructure from Trait<AssocTy = T>
(assuming we don't even need to handle trait objects).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@petrochenkov You are more familiar with the inner workings of the compiler, so I think you can better specify the proper desugaring. Could you PR against our PR perhaps?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Patch in: Centril#2
Associated type bounds: Fix desugaring + other house keeping
Status update: We (me and @joshtriplett) have patched the reference and have made consequence-changes in other places + some housekeeping. Hopefully everything should be up to snuff now. |
Personally, I'm not a big fan of this proposal. I just don't find it very obvious that a new name is being bound in the syntax as given, though I realize it is ambiguous, and also this is not a scenario that I think I've ever really noticed arising a lot. I'd be curious to see a set of examples from stdlib or other real crates to get some idea of how often it arises and how it feels in practice. |
@nikomatsakis I'll work on providing some more examples :) |
This is an extension of the |
@fstirlitz I find |
@nikomatsakis I run into this scenario quite often. I tend to write any function using the most general type I can, so I often want to say "an iterator over types that have this trait", for instance. |
@joshtriplett: I don't think grammars of programming languages should be designed based on how things 'directly translate' to natural languages, especially English. Natural languages are full of syntactic ambiguities and irregularities that are detrimental to reasoning about code. And I'd argue this particular form is one of the latter: |
Is this reasonable to do with ATCs ala I suppose the I'm sympathetic to not rushing to optimize syntax that expresses complex relationships, and initially reacted negatively to this proposal, but.. We're stretching I suppose |
To me, deprecation of
What's wrong with English with regard to ambiguity? French has its irregular verbs and "la", "le"; Swedish has its unintuitive (for a non-native speaker) "en" and "ett"...
They are full of syntactic ambiguities, but converting a subset which is unambiguous makes reasoning easier IMO. Being able to read code out loud in a way not too far from your mental model enhances understanding (and thus readability). This partly explains python's popularity due "executable pseudocode".
I think that the possible confusion here is approximates a one-time problem in learning Rust. Once you realize that type parameters correspond to relations (in mathematics) and that associated types correspond to functions (and crucially that Given that we already have |
@burdges It depends on what you wish to communicate. Consider: Consider: These two bounds are clearly different (even if the latter builds upon the combination of At this point; I think it is too early to introduce Regarding EDIT: Actually, no... that was wrong! I don't see Regarding fn foo() -> impl Iterator<Item = impl Clone> {
::std::iter::empty::<u8>()
} Therefore, the callee picks the type of If however you have fn bar(mut iter: impl Iterator<Item = impl Clone>) {
let x: Option<()> = iter.next();
} However; this snippet does not compile because the caller, and not the callee, gets to pick the type, and so we get: 10 | let x: Option<()> = iter.next();
| ^^^^^^^^^^^ expected (), found type parameter |
@Centril Thank you for the extensive work putting together that list of examples! (Was https://doc.rust-lang.org/nightly/src/alloc/vec.rs.html#2562 intended to link somewhere else? I don't see any instances in that source file to which this would apply.) |
@joshtriplett Oops; that one was derived :) |
@Centril That raises the question: How did you find these? |
@joshtriplett I checked the standard library for impls manually searching for |
@Centril Ah, I see. I suspect that other examples exist of the form https://github.com/rust-lang/rust/blob/1e01e22509df395fb42885235d694909b4309398/src/liballoc/tests/str.rs#L1385 Also, for future reference: |
This seems like it requires introducing a new feature to existential types that RFC 2071 doesn't specify, given fn foo() -> impl Iterator<Item: Display> { ... } this RFC appears to specify the desugaring as existential type _0: Iterator where <_0 as Iterator>::Item: Display;
fn foo() -> _0 { ... } EDIT: or is it existential type _0: Iterator;
fn foo() -> _0 where <_0 as Iterator>::Item: Display { ... } either way I'm not sure what the intended semantics of these statements would be. |
I would like to note that I'm +1 on this syntax for generic type parameters, it's just the barely mentioned extension to existential types that seems underspecified. Also, looking through to #1093 since that was linked, the desugaring for associated types in traits mentioned there doesn't seem to be specified. Given trait Foo {
type Bar: Iterator<Item: Display>;
} what does this desugar to, and how does that differ in practice/why would you choose that over the current trait Foo {
type BarItem: Display;
type Bar: Iterator<Item = Self::BarItem>;
} EDIT: or the other possibility in current rust, which I don't recall seeing used in the wild: trait Foo where <Self::Bar as Iterator>::Item: Display {
type Bar: Iterator;
} |
@Nemo157 While me and @joshtriplett discuss how to make this RFC clearer, let me talk a bit about the semantics in the cases you brought up for now. In the first case of The latter case can be desugared directly to: use std::fmt::Display;
trait Foo {
type Bar: Iterator where
<Self::Bar as Iterator>::Item: Display;
}
impl Foo for () {
type Bar = ::std::iter::Empty<u8>;
} Operationally speaking, it should have the same affect as having the two associated types (but where one is uninteresting wrt. public interface, which is also why you'd want to do this). |
The lang team spent a while discussing this RFC in our most recent meeting:
All told, we felt ready to move toward final review: @rfcbot fcp merge |
Team member @aturon 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. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
I just talked to @Centril about one thing that haven't been brought up yet: associated types in HRTBs. trait Foo<'a> {
type Out;
}
fn bar<T>()
where
T: for<'a> Foo<'a>,
for<'a> <T as Foo<'a>>::Out: Clone,
{} With this RFC, the bounds on where
T: for<'a> Foo<'a, Out: Clone>, Much better! |
The final comment period, with a disposition to merge, as per the review above, is now complete. |
Huzzah! This RFC is merged! Tracking issue: rust-lang/rust#52662 |
We're going to find some compiler bugs thanks to this. I wanted to abstract over collection types by using
so I wrote it out long hand like
It passes
In this case rustc is clearly wrong because See commit: burdges/pairing@6b53813 |
@nikomatsakis ^^ (lack of) lazy normalization strikes again? |
RENDERED
TRACKING ISSUE
Introduce the bound form
MyTrait<AssociatedType: Bounds>
, permitted anywhere a bound of the formMyTrait<AssociatedType = T>
would be allowed. The boundT: Trait<AssociatedType: Bounds>
desugars to the boundsT: Trait
and<T as Trait>::AssociatedType: Bounds
.An example:
This RFC was co-authored with @joshtriplett whom it was my pleasure to work with.