Allow `Self` to appear in the where clause of trait impls #1647

Merged
merged 2 commits into from Jan 6, 2017

Projects

None yet
@sgrif
Contributor
sgrif commented Jun 13, 2016

Rendered

@durka
Contributor
durka commented Jun 13, 2016 edited

Slightly related: #1588

This seems like a good idea. The only backcompat thing I could come up with is this, and it already doesn't compile, so no issue:

trait Foo {
    fn foo(&self);
}

trait Bar {}

struct Baz;
struct Quux;

impl Foo for Baz {
    fn foo(&self) {
        impl Bar for Quux where Self: Sized {}
            // before this RFC: erroneous attempt to use outer `Self` (i.e. `Baz`)
            // after this RFC: `Self` here refers to `Quux`
    }
}

fn main() {}
@nrc nrc added the T-lang label Jun 14, 2016
@Ericson2314
Contributor
Ericson2314 commented Jun 14, 2016 edited

IIUC, the associated type thing is weird in that <Self as Trait>::Assoc: Bound will sometimes fail, but then <Self as Trait>::Assoc isn't bound to begin with.

@sgrif
Contributor
sgrif commented Jun 14, 2016

Not really any different than having T: Trait, <T as Trait>::Assoc: Bound where T: Trait doesn't hold.

@Ericson2314
Contributor

Say a different impl with a different associated type succeeds?

@Ericson2314
Contributor

If <SelfWrittenOut as Trait>::Assoc: Bound is already allowed, then nevermind.

@sgrif
Contributor
sgrif commented Jun 17, 2016

<ExplicitSelfType as Trait>::Assoc: Bound is already allowed. I could probably sum this up more easily in that the goal is to have Self in impl Trait for T where Self: IsUsedHere to mean the same thing as fn method_in_trait_body() where Self: IsUsedHere in effectively every way

@Ericson2314 Ericson2314 commented on the diff Jun 18, 2016
text/0000-allow-self-in-where-clauses.md
+```
+
+but this will fail to compile today, forcing you to repeat the type, and adding
+one more place that has to change if the type ever changes.
+
+By this same logic, we would also like to be able to reference associated types
+from the traits being implemented. When dealing with generic code, patterns like
+this often emerge:
+
+```rust
+trait MyTrait {
+ type MyType: SomeBound;
+}
+
+impl<T, U, V> MyTrait for SomeStruct<T, U, V> where
+ SomeOtherStruct<T, U, V>: SomeBound,
@Ericson2314
Ericson2314 Jun 18, 2016 Contributor

If you could change this from SomeOtherStruct<T, U, V> to <Self as MyTrait>::MyType, that would make clear the current verbosity is not quite as bad :).

@Ericson2314
Contributor
Ericson2314 commented Jun 18, 2016 edited

@sgrif ah, sorry for the noise then. I think the goal is clear enough, but I commented on one line you might consider changing then to reflect the status-quo as you just clarified it to be.


All that straightened out, I am definitely for this!

@aturon aturon self-assigned this Jun 23, 2016
@nikomatsakis
Contributor

In general we already allow Self inside impls, iirc, so I am a bit surprised that it doesn't work in where clauses. Seems like an oversight or bug to me, to be honest.

When it comes to associated types, though, I am a bit dubious about this paragraph:

Accessing associated types will have the same result as copying the body of the associated type into the place where it's being used. That is to say that it will assume that all constraints hold, and evaluate to what the type would have been in that case. Ideally one should never have to write ::SomeType, but in practice it will likely be required to remove issues with recursive evaluation.

This seems to suggest some kind of distinct pathway for handling associated types in this location. I am not too keen on that, I would prefer for this to fall out from normalization (and indeed I think it very well might, though there may be some issues with resolving recursive references -- we could probably do better in this respect than we do in general).

@withoutboats withoutboats added I-nominated and removed I-nominated labels Nov 2, 2016
@withoutboats

@rfcbot fcp merge

(I hope I'm doing this right!)

I agree with @nikomatsakis that this seems like a bug. The 'type header' of an impl block (its where clauses, etc) should be considered a part of that impl block, and the Self type should be understood there as the receiving type.

@withoutboats
withoutboats commented Nov 2, 2016 edited

@rfcbot concern "Include type parameters"

I think this should also be accepted:

trait Foo<T> { }

struct Bar;

impl Foo<Self> for Bar { }
// equivalent to:
impl Foo<Bar> for Bar { }

And this:

trait Foo { }

struct Bar<T>(Box<T>);

impl Foo for Bar<Self> { }
// equivalent to
impl Foo for Bar<Bar> { }
@petrochenkov
Contributor
petrochenkov commented Nov 2, 2016 edited

EDIT: @withoutboats was faster at typing, but I'll still write this.

Technically, Self can be used in the "whole impl" except for the impl type itself, this includes the trait and bounds on impl parameters:

trait Tr<T> {}

impl<T: Tr<Self>> Tr<Self> for u8 {}

The only question is "How confusing this may be?", especially if the impl lives in another impl with its own Self.
The impl syntax is arbitrary, if it looked like impl<T> u8 as Trait<Self> where T: Tr<Self>, probably it would look more intuitive, because Self is used after the perceived point of "type introduction".

@petrochenkov
Contributor

@withoutboats

impl Foo for Bar<Self> { }
// equivalent to
impl Foo for Bar<Bar<Self>> { }
// equivalent to
impl Foo for Bar<Bar<Bar<Self>>> { }
// equivalent to
impl Foo for Bar<Bar<Bar<Bar<Self>>>> { }
// equivalent to
impl Foo for Bar<Bar<Bar<Bar<Bar<Self>>>>> { }
...
@withoutboats
withoutboats commented Nov 2, 2016 edited

@petrochenkov of course 😅 it should fail with some recursive type definition error, and not a "use of Self outside of an impl or trait" error

@petrochenkov
Contributor
petrochenkov commented Nov 2, 2016 edited

fail with some recursive type definition error, and not a "use of Self outside of an impl or trait" error

That... sounds very reasonable.
EDIT: I only hope impl Self {} won't create a black hole.

@rfcbot
rfcbot commented Nov 2, 2016 edited

Team member @withoutboats has proposed to merge this. The next step is review by the rest of the tagged teams:

Concerns:

Once these reviewers reach consensus, 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.

@nikomatsakis
Contributor
nikomatsakis commented Nov 4, 2016 edited

Wait, I am confused. The RFC concerns using Self in where-clauses, right? Do we intend to permit impl Trait<Self> for u8? (Do we already?)

@withoutboats

@nikomatsakis I would like to see the RFC amended to support that.

@petrochenkov
Contributor

For reference, trait items, unlike impl items, already permit Self everywhere and not only inside of the block.
This, for example, compiles:

trait Tr<T: Tr<Self> = Self> where Self: Copy {}
@nikomatsakis
Contributor

@withoutboats huh, really? Interesting. I guess it seems intuitive enough what it means, even if I find it a bit surprising. Presumably Self would also be "in scope" for bounds, right?

e.g.,

impl<T: Foo<Self>> Bar for u8 
@withoutboats
withoutboats commented Nov 8, 2016 edited

@nikomatsakis Yea. I agree that it seems weird to actually do, but we have a history of Self not always working for no particular reason, which IMO teaches users that Rust has complicated and arbitrary rules. :-\ I'd like to see the system be consistent between impls and traits - in any type position in any impl or trait, Self is the receiving type.

@nikomatsakis
Contributor

@withoutboats yeah, seems ok. I can get behind it.

@withoutboats

@rfcbot resolved "Include type parameters"

@rfcbot
rfcbot commented Dec 10, 2016

🔔 This is now entering its final comment period, as per the review above. 🔔

@briansmith
briansmith commented Dec 30, 2016 edited

@sgrif Could you please write a patch to the Rust Reference for this change? Perhaps it would be an empty patch since the Rust Reference already says “All traits define an implicit type parameter Self that refers to ʻthe type that is implementing this interface,ʻ" which supports the conclusion above that this is fixing a bug.

@nrc nrc added a commit to nrc/rfcs that referenced this pull request Jan 6, 2017
@nrc nrc Allow `Self` to appear in the where clause of trait impls (#1647)
Merges #1647
9ccd885
@nrc nrc merged commit 8e737ce into rust-lang:master Jan 6, 2017
@ubsan
Contributor
ubsan commented Jan 6, 2017

Yay!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment