rust icon indicating copy to clipboard operation
rust copied to clipboard

Tracking issue for trait aliases

Open withoutboats opened this issue 8 years ago • 123 comments

This is a tracking issue for trait aliases (rust-lang/rfcs#1733).

TODO:

  • [x] Implement: tracking issue
  • [x] #56485 — Bringing a trait alias into scope doesn't allow calling methods from its component traits (done in https://github.com/rust-lang/rust/pull/59166)
  • [x] #56488 — ICE with trait aliases and use items
  • [x] #57023 — Nightly Type Alias Compiler panic unexpected definition: TraitAlias
  • [x] #57059 — INCOHERENT_AUTO_TRAIT_OBJECTS future-compatibility warning (superseded by https://github.com/rust-lang/rust/pull/56481)
  • [ ] Document
  • [ ] Stabilize

Unresolved questions:

  • [ ] Are bounds on other input types than the receiver enforced or implied?
  • [ ] Should we keep the current behaviour of "shallow-banning" ?Sized bounds in trait objects, ban them "deeply" (through trait alias expansion), or permit them everywhere with no effect?
  • [ ] Insufficient flexibility
  • [ ] Inconvenient syntax
  • [ ] Misleading naming
  • [ ] Which of constraint/bound/type aliases are desirable

withoutboats avatar Apr 24 '17 22:04 withoutboats

I think #24010 (allowing aliases to set associated types) should be mentioned here.

durka avatar May 07 '17 21:05 durka

I'd like to take a crack at this (starting with parsing).

durka avatar Oct 02 '17 09:10 durka

I read the RFC and I saw a call out to Service, but I am not sure if the RFC actually solves the Service problem.

Specifically, the "alias" needs to provide some additional associated types:

See this snippet: https://gist.github.com/carllerche/76605b9f7c724a61a11224a36d29e023

Basically, you rarely want to just alias HttpService to Service<Request = http::Request> You really want to do something like this (while making up syntax):

trait HttpService = Service<http::Request<Self::RequestBody>> {
    type RequestBody;
}

In other words, the trait alias introduces a new associated type.

The reason why you can't do: trait HttpService<B> = Service<http::Request<B>> is that then you end up getting into the "the type parameter B is not constrained by the impl trait, self type, or predicates" problem.

carllerche avatar Nov 03 '17 04:11 carllerche

Basically, you rarely want to just alias HttpService to Service<Request = http::Request>

Rarely? How do you define that?

The syntax you suggest seems a bit complex to me and non-intuitive. I don’t get why we couldn’t make an exception in the way the “problem” shows up. Cannot we just hack around that rule you expressed? It’s not a “real trait”, it should be possible… right?

hadronized avatar Nov 03 '17 10:11 hadronized

@phaazon rarely with regards to the service trait. This was not a general statement for when you would want trait aliasing.

Also, the syntax was not meant to be a real proposal. It was only to illustrate what I was talking about.

carllerche avatar Nov 03 '17 16:11 carllerche

I see. Cannot we just use free variables for that? Like, Service<Request = http::Request> implies the free variable used in http::request<_>?

hadronized avatar Nov 03 '17 20:11 hadronized

@phaazon I don't understand this proposal.

carllerche avatar Nov 03 '17 22:11 carllerche

@durka: how's the work on the follow-up to https://github.com/rust-lang/rust/pull/45047 going?

varkor avatar Feb 21 '18 15:02 varkor

Something I mentioned in the RFC: trait Trait =; is accepted by the proposed grammar and I think that this is a bit weird. Perhaps maybe the proposed _ syntax might be more apt here, because I think that allowing empty trait requirements is useful.

clarfonthey avatar Feb 27 '18 17:02 clarfonthey

We can put a check for that in AST validation. However I suppose it could be useful for code generation if there's no special case, I dunno.

On Tue, Feb 27, 2018 at 12:48 PM, Clar Roʒe [email protected] wrote:

Something I mentioned in the RFC: trait Trait =; is accepted by the proposed grammar and I think that this is a bit weird. Perhaps maybe the proposed _ syntax might be more apt here, because I think that allowing empty trait requirements is useful.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/rust-lang/rust/issues/41517#issuecomment-368965137, or mute the thread https://github.com/notifications/unsubscribe-auth/AAC3n3HdG3ZOmbN2PKky7nFnx0WokY7Lks5tZD_fgaJpZM4NGzYc .

durka avatar Feb 27 '18 17:02 durka

One other thing to note as a general weirdness is macro expansions involving trait bounds. Currently, fn thing<T:>() is valid syntax but perhaps fn thing<T: _>() should be the recommended version.

But then, is _ + Copy or something okay? I'm not sure. I would just suggest Any but that has different guarantees.

clarfonthey avatar Feb 27 '18 17:02 clarfonthey

Empty bound lists (and other lists) are accepted in other contexts as well, e.g. fn f<T: /*nothing*/>() { ... }, so trait Trait = /*nothing*/; being accepted is more of a rule than an exception.

petrochenkov avatar Feb 27 '18 17:02 petrochenkov

I think it makes sense being accepted, although I wonder if making it the canonical way to do so outside of macros is the right way to go. We already have been pushing toward '_ for elided lifetimes in generics, for example.

clarfonthey avatar Feb 27 '18 17:02 clarfonthey

So I was thinking of working on this one because it's a feature I really want and it seems relatively straightforward, but I'm not exactly sure where to start. I haven't done any compiler work before.

It seems like a good first implementation would be to basically convert trait aliases to a trait and blanket impl in the HIR, but I'm not sure if that would actually work.

clarfonthey avatar May 04 '18 19:05 clarfonthey

@clarcharr That sounds like a good plan to me, on first sight, though I'm not the most knowledgeable about this. I wonder if someone could help you by mentoring. Maybe get yourself on the Discord #lang channel and ask? :-) (Also, I might even even contribute here myself, given this seems to be a fair bit of work.)

alexreg avatar Jun 11 '18 02:06 alexreg

One thing I don't see mentioned in the RFC is whether useing trait aliases brings methods into scope? (see the next comment for a correct example, mine was wrong).

Nemo157 avatar Jul 09 '18 17:07 Nemo157

@Nemo157 In your example you are using a trait object, which should work (as well as generics) without importing the trait, like this:

mod some_module {
    pub trait Foo {
        fn foo(&self);
    }
}

fn use_foo(object: &some_module::Foo) {
    object.foo();
}

The question is whether this should work:

mod some_module {
    pub trait Foo {
        fn foo(&self);
    }
    
    pub trait Bar {
        fn bar(&self);
    }
    
    pub struct Qux;
    
    impl Foo for Qux {
        fn foo(&self) {}
    }
    
    impl Bar for Qux {
        fn bar(&self) {}
    }
    
    pub trait Baz = Foo + Bar;
}

use some_module::{Baz, Qux};

fn use_baz(object: Qux) {
    // Should this work because Baz is in scope?
    object.foo();
    object.bar();
}

pengowen123 avatar Jul 09 '18 18:07 pengowen123

Considering I want this more than even async/await, I'll try diving in here. Are there any possible mentoring or hints as to where to proceed? It looks like @durka got parsing in place, and in typeck collect, it specifically says it's not implemented. I'll likely start there, trying to copy what a regular trait does, and then watch as ICEs pile up...

It would be useful to update the issue description with more details about the current status. It took me a good half hour to figure this much out:

  • Parsing to AST is complete.
  • Lowering to HIR is... in place. It doesn't fully do everything, but it's further along than simply libsyntax knowing how to parse it.

If specific issues could also be listed, it'd help keep track of the work remaining.

seanmonstar avatar Jul 23 '18 17:07 seanmonstar

@seanmonstar Awesome! Your impression seems right. I couldn't get it integrated in the trait resolver at all. Here is some old advice from @nikomatsakis. Suggestions I got didn't work, and it seemed like chalk was going to come and change the whole system -- but I have no idea how that's progressing.

durka avatar Jul 23 '18 23:07 durka

@seanmonstar Sounds good! I too have heard that Chalk may be the way forward with implementing something like this, but I’m not completely sure. Anyway, depending on how much work ypu feel this may be, I’d be glad to chip in if you like.

alexreg avatar Aug 03 '18 04:08 alexreg

I want to clarify something that I don't think was properly addressed in the original RFC discussions. From what I gather, the feature that has been accepted is not "trait aliases", but "bounds aliases". However, the proposals to use keywords other that trait for this seem to have been completely ignored.

From experience, using incorrect terminology for things causes problems down the line that we might not guess at now, because it introduces inconsistencies (especially with a keyword that is already used for something else: specifically just traits).

Personally, I'd really appreciate a convincing argument for why trait is an acceptable keyword for bound aliases, or a proposal to change the syntax to something like bounds (or constraints, or really anything more general) before stabilisation.

varkor avatar Aug 05 '18 14:08 varkor

@varkor Fair point. I agree bounds would be a more accurate name, and lead to less confusion.

alexreg avatar Aug 05 '18 16:08 alexreg

FWIW, I don't think using trait is actually that confusing. Type aliases do not define new types themselves, and yet we use type Foo = .. syntax. Trait aliases may have edge cases where they don't include any traits (?Sized + 'static or something), but that doesn't mean they won't appear to function as traits or even necessarily need to use a different keyword.

As example of a related place where people use slightly incorrect terminology to no ill effect, we often hear the bounds on a trait's Self parameter referred to using the language of inheritance- "base trait," "supertrait," etc. Despite the fact that there's no subclassing going on, and currently not even a way to downcast trait objects, this is still fine.

rpjohnst avatar Aug 05 '18 17:08 rpjohnst

but that doesn't mean they won't appear to function as traits or even necessarily need to use a different keyword.

I think trait is very misleading, precisely because this point is false. If "trait aliases" worked like type aliases, then you would be able to do something like the following:

trait Trait {}

trait Alias = Trait;

impl Alias for () {}

As it stands, you cannot, because so-called "trait" aliases are actually bounds aliases. Traits and bounds are different (even specifically trait bounds), both from an abstract model point-of-view, and their functionality within the language.

Using the term "trait alias" for this terminology is confusing and prevents true trait aliases from being added in a consistent manner with type aliases.

As example of a related place where people use slightly incorrect terminology to no ill effect

The use of terminology by people is very different to its realisation in the language. People might say "trait" instead of "trait bound" in practice, but conflating the two inside the language is, I would argue, a mistake.

varkor avatar Aug 05 '18 17:08 varkor

I believe that in practice there shouldn't be a visible difference between trait Alias: Trait where Self: Other {} impl<T: Trait> Alias for T where Self: Other {} and trait Alias = Trait.

So, in this case, impl Alias doesn't even make sense, because you've already defined a blanket impl.

clarfonthey avatar Aug 05 '18 17:08 clarfonthey

@clarcharr: I'm thinking more in terms of renaming, like in the following:

struct S;
trait T1 {}
trait T2 = T1;
impl T2 for S {}

This is the intuitive meaning (as far as I can see it) of the term "trait alias". It's analogous to how type aliases work. Note that you would also be able to use these trait aliases in bounds (aliases would be functionally identical to traits).

Whereas "bounds aliases" would alias both traits and lifetimes, specifically in the context of bounds on generic parameters, etc.).

varkor avatar Aug 05 '18 18:08 varkor

I assume you mean impl T1 for dyn T2, right? Or impl<T: T2> T1 for T {} ?

The latter will work as expected. Although the former basically, what you're asking is the meaning of what impl T2 and dyn T2 mean. Which… again, I think work in the context of trait aliases as both trait aliases and bound aliases.

clarfonthey avatar Aug 05 '18 18:08 clarfonthey

@clarcharr: sorry, I got that line completely muddled. I've updated it. I mean that we implement a trait by using its alias. (Obviously in this example, there's no point, but you could imagine something with generic parameters, etc. being useful to use in this way.)

varkor avatar Aug 05 '18 20:08 varkor

anecdata, but I would find bounds to be more confusing than trait.

glandium avatar Aug 05 '18 21:08 glandium

We actually did go back and forth on whether to use the keyword bound or maybe constraint in the RFC, but didn't come to a firm conclusion. Personally I don't see the huge need for a new keyword. You can use a trait as a bound... you can use a trait alias as a bound.

Would it make it better if you could impl an alias? We talked about that too but there are some gotchas. I very much encourage those now debating "trait"/"bound" to read these comments as some issues were already brought up.

The important thing is to get it implemented so we can experiment with these things.

durka avatar Aug 06 '18 03:08 durka