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

Trait alias. #1733

Merged
merged 30 commits into from Apr 24, 2017

Conversation

@phaazon
Contributor

phaazon commented Aug 31, 2016

Rendered


Trait alias would enable aliasing traits.

Useful especially when we want to lift the name from generic to specific (a bit like Result on a lot of modules, but for traits):

mod gen {
  trait Foo<T> {
    // ...
  }
}

mod spec {
  struct Bck0 {}

  trait Foo as gen::Foo<Bck0>;
}
Show outdated Hide outdated text/0000-trait-alias.md
The idea is to add a new keyword or construct for enabling trait aliasing. One shouldn’t use the
`type` keyword as a trait is not a type and that could be very confusing.
The `trait TraitAlias as Trait` is suggested as a starter construct for the discussion.

This comment has been minimized.

@mcarton

mcarton Aug 31, 2016

Why not trait TraitAlias = Trait similar to the type keyword's syntax?
If I came across trait Foo as Bar I would probably read it as Bar being an alias for Foo.

@mcarton

mcarton Aug 31, 2016

Why not trait TraitAlias = Trait similar to the type keyword's syntax?
If I came across trait Foo as Bar I would probably read it as Bar being an alias for Foo.

This comment has been minimized.

@durka

durka Aug 31, 2016

Contributor

I like the = syntax to mirror type aliases as well.

@durka

durka Aug 31, 2016

Contributor

I like the = syntax to mirror type aliases as well.

This comment has been minimized.

@phaazon

phaazon Sep 1, 2016

Contributor

Yeah, I think it’s a good idea as well, as would read the other way around, yeah.

@phaazon

phaazon Sep 1, 2016

Contributor

Yeah, I think it’s a good idea as well, as would read the other way around, yeah.

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Aug 31, 2016

Contributor

Note that this is sugar for

trait Foo: gen::Foo<Bck0> {}
impl<T: gen::Foo<Bck0>> Foo for T {}

So since the functionality is already supported, it makes sense to add syntax. The = syntax would be intuitively analogous to type alias syntax (indeed, I've seen people ask why it doesn't work already). 👍

Contributor

durka commented Aug 31, 2016

Note that this is sugar for

trait Foo: gen::Foo<Bck0> {}
impl<T: gen::Foo<Bck0>> Foo for T {}

So since the functionality is already supported, it makes sense to add syntax. The = syntax would be intuitively analogous to type alias syntax (indeed, I've seen people ask why it doesn't work already). 👍

@nrc nrc added the T-lang label Aug 31, 2016

@phaazon

This comment has been minimized.

Show comment
Hide comment
@phaazon

phaazon Sep 1, 2016

Contributor

@durka, hm, are you sure it safely desugars to that? It creates a brand new trait, not really an alias.

Contributor

phaazon commented Sep 1, 2016

@durka, hm, are you sure it safely desugars to that? It creates a brand new trait, not really an alias.

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Sep 1, 2016

Member

@durka You can't implement Foo with gen::Foo's methods though.

Member

eddyb commented Sep 1, 2016

@durka You can't implement Foo with gen::Foo's methods though.

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Sep 1, 2016

Contributor

You're right, the trait aliasing that I demonstrated only works for bounds. I didn't realize that this RFC would allow impl blocks as well. The RFC could benefit from an example!

Contributor

durka commented Sep 1, 2016

You're right, the trait aliasing that I demonstrated only works for bounds. I didn't realize that this RFC would allow impl blocks as well. The RFC could benefit from an example!

@Diggsey

This comment has been minimized.

Show comment
Hide comment
@Diggsey

Diggsey Sep 6, 2016

Contributor

@durka More importantly, the Foo and gen::Foo<Bck0> trait objects are incompatible, since rust does not support converting between trait objects.

I prefer the trait X = Y syntax for symmetry with type aliases, and it should support generics too, eg. trait X<T> = Y<T, Foo>

Also, will this be able to support multiple traits, ie. can I do trait X = Y + Z? While powerful and very useful it would be confusing that you couldn't use trait X as a trait object.

Contributor

Diggsey commented Sep 6, 2016

@durka More importantly, the Foo and gen::Foo<Bck0> trait objects are incompatible, since rust does not support converting between trait objects.

I prefer the trait X = Y syntax for symmetry with type aliases, and it should support generics too, eg. trait X<T> = Y<T, Foo>

Also, will this be able to support multiple traits, ie. can I do trait X = Y + Z? While powerful and very useful it would be confusing that you couldn't use trait X as a trait object.

@phaazon

This comment has been minimized.

Show comment
Hide comment
@phaazon

phaazon Sep 6, 2016

Contributor

I think trait X = Y + Z should be supported as well, a bit like Haskell with the ConstraintKinds ghc extension that can be used to enable constraint aliasing (in which you find typeclasses).

Contributor

phaazon commented Sep 6, 2016

I think trait X = Y + Z should be supported as well, a bit like Haskell with the ConstraintKinds ghc extension that can be used to enable constraint aliasing (in which you find typeclasses).

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Sep 6, 2016

Contributor

The thing about trait X = Y + Z; is this:

In the simpler case of trait A = B;, then either A is a separate trait or it isn't. The "desugaring" I proposed (trait A: B {} impl<T: B> A for B {}) would make A and B separate traits so you wouldn't be able to cast between the trait objects. But it sounds like this RFC is going in the direction that they would actually be synonyms, so my desugaring doesn't quite work.

But Rust currently doesn't have any way to make a trait object of two non-builtin traits, like &(Y + Z). Therefore something like my desugaring would be the only way to make trait X = Y + Z; work.

Sure, we could have the same syntax do two different things, but that seems weird. It seems to me that for now we should just do trait A = B;, and then in the glorious future when Rust gains the ability to represent &(Y + Z), then trait X = Y + Z; automatically starts working.

Contributor

durka commented Sep 6, 2016

The thing about trait X = Y + Z; is this:

In the simpler case of trait A = B;, then either A is a separate trait or it isn't. The "desugaring" I proposed (trait A: B {} impl<T: B> A for B {}) would make A and B separate traits so you wouldn't be able to cast between the trait objects. But it sounds like this RFC is going in the direction that they would actually be synonyms, so my desugaring doesn't quite work.

But Rust currently doesn't have any way to make a trait object of two non-builtin traits, like &(Y + Z). Therefore something like my desugaring would be the only way to make trait X = Y + Z; work.

Sure, we could have the same syntax do two different things, but that seems weird. It seems to me that for now we should just do trait A = B;, and then in the glorious future when Rust gains the ability to represent &(Y + Z), then trait X = Y + Z; automatically starts working.

@Diggsey

This comment has been minimized.

Show comment
Hide comment
@Diggsey

Diggsey Sep 6, 2016

Contributor

@durka You're forgetting that traits are not just used as trait objects. trait X = Y + Z can work perfectly fine for static polymorphism, it just means that &X is not a valid trait object (just as &(Y + Z) is not a valid trait object.

Contributor

Diggsey commented Sep 6, 2016

@durka You're forgetting that traits are not just used as trait objects. trait X = Y + Z can work perfectly fine for static polymorphism, it just means that &X is not a valid trait object (just as &(Y + Z) is not a valid trait object.

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Sep 6, 2016

Contributor

@Diggsey I didn't forget, but I glossed over it :) For all purposes besides trait objects, my desugaring works and trait X = Y + Z; would be fine. But it seems weird that trait X = sometimes makes an objectifiable trait and sometimes not, depending on the RHS. But I guess that's the same kind of inconsistency as object safety anyway, so perhaps it's not so bad.

Contributor

durka commented Sep 6, 2016

@Diggsey I didn't forget, but I glossed over it :) For all purposes besides trait objects, my desugaring works and trait X = Y + Z; would be fine. But it seems weird that trait X = sometimes makes an objectifiable trait and sometimes not, depending on the RHS. But I guess that's the same kind of inconsistency as object safety anyway, so perhaps it's not so bad.

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Sep 7, 2016

Member

&(X + Y) can be supported if a subset of #1524 is accepted (making &(X + Y) a triple-word fat pointer) 😉

Member

kennytm commented Sep 7, 2016

&(X + Y) can be supported if a subset of #1524 is accepted (making &(X + Y) a triple-word fat pointer) 😉

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Sep 8, 2016

Contributor

What about HRTB? Would this be viable?

trait Bar<'a> { }

trait Foo = for<'a> Bar<'a>;
Contributor

withoutboats commented Sep 8, 2016

What about HRTB? Would this be viable?

trait Bar<'a> { }

trait Foo = for<'a> Bar<'a>;
@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Sep 8, 2016

Member

@withoutboats From an implementation standpoint, it shouldn't be more difficult than other trait aliases.
And I can certainly see its value, I've noticed people building helper traits for this in various situations.

Member

eddyb commented Sep 8, 2016

@withoutboats From an implementation standpoint, it shouldn't be more difficult than other trait aliases.
And I can certainly see its value, I've noticed people building helper traits for this in various situations.

@solson

This comment has been minimized.

Show comment
Hide comment
@solson

solson Sep 14, 2016

Member

A concrete example for hiding HRTB came up in #rust:

trait FromRef<T> = for<'a> From<&'a T>;
Member

solson commented Sep 14, 2016

A concrete example for hiding HRTB came up in #rust:

trait FromRef<T> = for<'a> From<&'a T>;
@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Sep 14, 2016

Member

@solson

Would impl FromRef<u32> for SomeStruct { ... } work? (How would the signature of from be written though?)

Could the compiler auto-translate it to impl<'a> From<&'a u32> for SomeStruct { ... }?

Member

kennytm commented Sep 14, 2016

@solson

Would impl FromRef<u32> for SomeStruct { ... } work? (How would the signature of from be written though?)

Could the compiler auto-translate it to impl<'a> From<&'a u32> for SomeStruct { ... }?

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Sep 14, 2016

Contributor

I don't know how implementing FromRef would work, and it concerns me.

Here's From<&'a u32>.

impl<'a> From<&'a u32> for u32 {
    fn from(x: &'a u32) -> u32 { *x }
}

Note that the 'a shows up in the fn body. Here is FromRef<u32>:

impl FromRef<u32> for u32 {
    fn from(x: &u32) -> u32 { *x }
}

The normal meaning of this would be to elide a lifetime parameter on the from method, having an incompatible signature with the trait.

Special elisions for implementing HRTB trait aliases don't seem like a good idea to me (and AFAICT can't scale to an HRTB trait alias with multiple higher ranked lifetimes), but not being able to implement a trait alias transparently also seems like a problem.

Contributor

withoutboats commented Sep 14, 2016

I don't know how implementing FromRef would work, and it concerns me.

Here's From<&'a u32>.

impl<'a> From<&'a u32> for u32 {
    fn from(x: &'a u32) -> u32 { *x }
}

Note that the 'a shows up in the fn body. Here is FromRef<u32>:

impl FromRef<u32> for u32 {
    fn from(x: &u32) -> u32 { *x }
}

The normal meaning of this would be to elide a lifetime parameter on the from method, having an incompatible signature with the trait.

Special elisions for implementing HRTB trait aliases don't seem like a good idea to me (and AFAICT can't scale to an HRTB trait alias with multiple higher ranked lifetimes), but not being able to implement a trait alias transparently also seems like a problem.

@Diggsey

This comment has been minimized.

Show comment
Hide comment
@Diggsey

Diggsey Sep 14, 2016

Contributor

@withoutboats I don't see a problem with not being able to implement a trait alias if you can't implement what it aliases to to start with.

It's not like you can impl for<'a> From<&'a u32> for u32 anyway.

Contributor

Diggsey commented Sep 14, 2016

@withoutboats I don't see a problem with not being able to implement a trait alias if you can't implement what it aliases to to start with.

It's not like you can impl for<'a> From<&'a u32> for u32 anyway.

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Sep 14, 2016

Contributor

impl for<'a> From<&'a u32> is morally equivalent to impl<'a> From<&'a u32> and it presents a pretty serious usability problem if this feature is used to obscure HRTB but it is unclear to users how they could provide an impl that satisfies this trait bound.

Contributor

withoutboats commented Sep 14, 2016

impl for<'a> From<&'a u32> is morally equivalent to impl<'a> From<&'a u32> and it presents a pretty serious usability problem if this feature is used to obscure HRTB but it is unclear to users how they could provide an impl that satisfies this trait bound.

@Diggsey

This comment has been minimized.

Show comment
Hide comment
@Diggsey

Diggsey Sep 15, 2016

Contributor

@withoutboats that's already the case for trait inheritance. For example, you can't implement Eq without implementing PartialEq, and you have to look at the definition of Eq to figure that out. With trait where clauses it can be even more complicated.

Contributor

Diggsey commented Sep 15, 2016

@withoutboats that's already the case for trait inheritance. For example, you can't implement Eq without implementing PartialEq, and you have to look at the definition of Eq to figure that out. With trait where clauses it can be even more complicated.

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Sep 15, 2016

Contributor

Sure, but in that case its relatively easy to learn what trait you need to implement. I'm concerned that using this feature to obscure HRTB will lead to a situation where a less expert Rust user sees that they need to implement FromRef, sees in the definition of FromRef trait FromRef<T> = for<'a> From<&'a T>; and then is baffled about how they could implement for<'a> From<&'a T>.

I don't have a solution; HRTB just present a pretty serious accessibility problem in general.

Contributor

withoutboats commented Sep 15, 2016

Sure, but in that case its relatively easy to learn what trait you need to implement. I'm concerned that using this feature to obscure HRTB will lead to a situation where a less expert Rust user sees that they need to implement FromRef, sees in the definition of FromRef trait FromRef<T> = for<'a> From<&'a T>; and then is baffled about how they could implement for<'a> From<&'a T>.

I don't have a solution; HRTB just present a pretty serious accessibility problem in general.

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Sep 15, 2016

Member

Current situation:

Ability Simple trait HRTB (for<'a> T<'a>) Composite (X + Y)
Constraint S: T
Impl trait -> impl T
Trait object &T
impl T for S
Member

kennytm commented Sep 15, 2016

Current situation:

Ability Simple trait HRTB (for<'a> T<'a>) Composite (X + Y)
Constraint S: T
Impl trait -> impl T
Trait object &T
impl T for S
@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Sep 15, 2016

Contributor

Is type FromRef = for<'a> From<&'a T>; needed if you can write type FromRef<'a> = From<&'a T>;?

Contributor

durka commented Sep 15, 2016

Is type FromRef = for<'a> From<&'a T>; needed if you can write type FromRef<'a> = From<&'a T>;?

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Sep 15, 2016

Member

@durka The point is to hide the for<'a> part, just like you can write F: Fn(&u32) -> &u32 without any explicit lifetime.

Member

kennytm commented Sep 15, 2016

@durka The point is to hide the for<'a> part, just like you can write F: Fn(&u32) -> &u32 without any explicit lifetime.

@aturon aturon self-assigned this Sep 29, 2016

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Nov 2, 2016

Contributor

I think I'm generally in favor of adding some kind of shorthand for "trait aliases" (or "where-clause aliases", depending on your POV). @aturon and I talked about it recently and we felt like the syntax ought to be modeled on the syntax for declaring a new trait with supertraits, but with = replacing :. So if you had a declaration like this:

trait SymPartialEq<T> = PartialEq<T> where T: PartialEq<Self>;

then you could do:

fn foo<T, U>() where T: SymPartialEq<U>

and it would be equivalent to

fn foo<T, U>() where T: PartialEq<U>, U: PartialEq<T>

I'm not sure what to do about the case where you want just a where-clause though, and no =. I don't like trait Foo<T> where T: Bar<Self>, because it looks too much like a declaration of a new trait. I guess trait Foo<T> = where T: Bar<Self> might be ok.

Not sure if I love the precise syntax here, but there is something nice about the symmetry with supertrait clauses, and the "bias" to always have a Self type, just like regular traits.

It seems good for the more common cases that don't require where clauses, e.g.

trait Foo = Bar + Baz;
Contributor

nikomatsakis commented Nov 2, 2016

I think I'm generally in favor of adding some kind of shorthand for "trait aliases" (or "where-clause aliases", depending on your POV). @aturon and I talked about it recently and we felt like the syntax ought to be modeled on the syntax for declaring a new trait with supertraits, but with = replacing :. So if you had a declaration like this:

trait SymPartialEq<T> = PartialEq<T> where T: PartialEq<Self>;

then you could do:

fn foo<T, U>() where T: SymPartialEq<U>

and it would be equivalent to

fn foo<T, U>() where T: PartialEq<U>, U: PartialEq<T>

I'm not sure what to do about the case where you want just a where-clause though, and no =. I don't like trait Foo<T> where T: Bar<Self>, because it looks too much like a declaration of a new trait. I guess trait Foo<T> = where T: Bar<Self> might be ok.

Not sure if I love the precise syntax here, but there is something nice about the symmetry with supertrait clauses, and the "bias" to always have a Self type, just like regular traits.

It seems good for the more common cases that don't require where clauses, e.g.

trait Foo = Bar + Baz;
@cuviper

This comment has been minimized.

Show comment
Hide comment
@cuviper

cuviper Nov 2, 2016

Member

Maybe trait Foo<T> = _ where T: Bar<Self> or trait Foo<T> = * where T: Bar<Self> ?

Member

cuviper commented Nov 2, 2016

Maybe trait Foo<T> = _ where T: Bar<Self> or trait Foo<T> = * where T: Bar<Self> ?

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Nov 2, 2016

Contributor

What would be the difference between trait X = where Z; and trait X where Z {}?

On Wed, Nov 2, 2016 at 4:48 PM, Josh Stone notifications@github.com wrote:

Maybe trait Foo = _ where T: Bar or trait Foo = * where T:
Bar ?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1733 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAC3nyx6H478DW2EceC6WEyOjopf7O_Uks5q6Pc5gaJpZM4JxjCo
.

Contributor

durka commented Nov 2, 2016

What would be the difference between trait X = where Z; and trait X where Z {}?

On Wed, Nov 2, 2016 at 4:48 PM, Josh Stone notifications@github.com wrote:

Maybe trait Foo = _ where T: Bar or trait Foo = * where T:
Bar ?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#1733 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AAC3nyx6H478DW2EceC6WEyOjopf7O_Uks5q6Pc5gaJpZM4JxjCo
.

@bluss

This comment has been minimized.

Show comment
Hide comment
@bluss

bluss Nov 2, 2016

How are associated types handled? I commonly use things like trait Scalar : Add<Output=Self> + Copy { } (with more operators).

bluss commented Nov 2, 2016

How are associated types handled? I commonly use things like trait Scalar : Add<Output=Self> + Copy { } (with more operators).

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Nov 4, 2016

Contributor

@durka

What would be the difference between trait X = where Z; and trait X where Z {}?

I guess "Z" is meant as a stand-in for some where-clause here, like Self: Clone? In that case, the difference is that trait X = where Self: Clone declares an alias, meaning that writing Foo: X is equivalent to writing Foo: Clone. In contrast, trait X where Self: Clone declares a new trait where implementing X requires implementing Clone.

If you added a blanket impl like impl<T: Clone> X for T then they are close to equivalent in effect, but not quite. In particular, trait X = where Self: Clone has an "if and only if" relationship (i.e., if T: X, then T: Clone, and vice versa). In contrast, if you declare a new trait, then you know that T: Clone => T: X but you don't know the reverse.

Contributor

nikomatsakis commented Nov 4, 2016

@durka

What would be the difference between trait X = where Z; and trait X where Z {}?

I guess "Z" is meant as a stand-in for some where-clause here, like Self: Clone? In that case, the difference is that trait X = where Self: Clone declares an alias, meaning that writing Foo: X is equivalent to writing Foo: Clone. In contrast, trait X where Self: Clone declares a new trait where implementing X requires implementing Clone.

If you added a blanket impl like impl<T: Clone> X for T then they are close to equivalent in effect, but not quite. In particular, trait X = where Self: Clone has an "if and only if" relationship (i.e., if T: X, then T: Clone, and vice versa). In contrast, if you declare a new trait, then you know that T: Clone => T: X but you don't know the reverse.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Nov 4, 2016

Contributor

@bluss

How are associated types handled? I commonly use things like trait Scalar : Add<Output=Self> + Copy { } (with more operators).

There's no problem there. You could write:

trait Scalar = Add<Output=Self> + Copy;

in which case, writing T: Scalar would be "as if" you wrote T: Add<Output=T> + Copy.

Contributor

nikomatsakis commented Nov 4, 2016

@bluss

How are associated types handled? I commonly use things like trait Scalar : Add<Output=Self> + Copy { } (with more operators).

There's no problem there. You could write:

trait Scalar = Add<Output=Self> + Copy;

in which case, writing T: Scalar would be "as if" you wrote T: Add<Output=T> + Copy.

@solson

This comment has been minimized.

Show comment
Hide comment
@solson

solson Nov 4, 2016

Member

@nikomatsakis To clarify, trait X = where Self: Clone is equivalent to (or the expanded form of) trait X = Clone?

I don't like trait Foo<T> where T: Bar<Self>, because it looks too much like a declaration of a new trait. I guess trait Foo<T> = where T: Bar<Self> might be ok.

I like having the =, since it reminds me of type T =. Then type trait and definitions never have =, but type and trait aliases always do.

It's a bit weird because the general form we're talking about right now is trait <name> = <self-bounds> where <general-bounds>. I thought of cleaning up the syntax by having only general-bounds, but then the simple cases have to mention Self:. :/

Member

solson commented Nov 4, 2016

@nikomatsakis To clarify, trait X = where Self: Clone is equivalent to (or the expanded form of) trait X = Clone?

I don't like trait Foo<T> where T: Bar<Self>, because it looks too much like a declaration of a new trait. I guess trait Foo<T> = where T: Bar<Self> might be ok.

I like having the =, since it reminds me of type T =. Then type trait and definitions never have =, but type and trait aliases always do.

It's a bit weird because the general form we're talking about right now is trait <name> = <self-bounds> where <general-bounds>. I thought of cleaning up the syntax by having only general-bounds, but then the simple cases have to mention Self:. :/

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Nov 4, 2016

Contributor

@solson

To clarify, trait X = where Self: Clone is equivalent to (or the expanded form of) trait X = Clone?

Yes, just as trait X: Clone and trait X where Self: Clone are equivalent.

I like having the =, since it reminds me of type T =. Then type trait and definitions never have =, but type and trait aliases always do.

Yes.

I thought of cleaning up the syntax by having only general-bounds

I thought of this too, but I found the example of trait Foo = Bar + Baz pretty compelling.

Contributor

nikomatsakis commented Nov 4, 2016

@solson

To clarify, trait X = where Self: Clone is equivalent to (or the expanded form of) trait X = Clone?

Yes, just as trait X: Clone and trait X where Self: Clone are equivalent.

I like having the =, since it reminds me of type T =. Then type trait and definitions never have =, but type and trait aliases always do.

Yes.

I thought of cleaning up the syntax by having only general-bounds

I thought of this too, but I found the example of trait Foo = Bar + Baz pretty compelling.

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Nov 21, 2016

Contributor

Been thinking about this, I'm in favor of it (with the = syntax) as a system for creating aliases to use in bounds. That is, I'm in favor of the right hand side of the expression being an arbitrary set of bounds, and not in favor of allowing users to ever impl an alias for a type.

I'm increasingly often seeing bounds that would benefit from this. I have some traits in my code like this:

trait HasOne<Rel: Relation> {
    ...
}

trait Relation {
    type Resource: Resource;
    ...
}

And I often want to bound a relation type with its resource implementing a certain trait. It would be great to reduce these bounds as in:

//from this
where T: HasOne<Rel>, Rel: Relation, Rel::Resource: Get;

//to this
trait GetRelated<Rel: Relation> = HasOne<Rel> where Rel::Resource: Get;

where T: GetRelated<Rel>

In the long (long) term, this introduces a syntax which could open the door for ConstraintKinds-like "associated traits" as in:

trait Foo {
    trait Constraint;
    fn foo<T: Self::Constraint>(&self, arg: T);
}

impl Foo for u32 {
    trait Constraint = Display;
    fn foo<T: Display>(&self, arg: T) { ... }
}

There's a lot of other questions around a feature like that, and I don't want to conflate this RFC with that idea, but I just want to note it as a potential extension.

Contributor

withoutboats commented Nov 21, 2016

Been thinking about this, I'm in favor of it (with the = syntax) as a system for creating aliases to use in bounds. That is, I'm in favor of the right hand side of the expression being an arbitrary set of bounds, and not in favor of allowing users to ever impl an alias for a type.

I'm increasingly often seeing bounds that would benefit from this. I have some traits in my code like this:

trait HasOne<Rel: Relation> {
    ...
}

trait Relation {
    type Resource: Resource;
    ...
}

And I often want to bound a relation type with its resource implementing a certain trait. It would be great to reduce these bounds as in:

//from this
where T: HasOne<Rel>, Rel: Relation, Rel::Resource: Get;

//to this
trait GetRelated<Rel: Relation> = HasOne<Rel> where Rel::Resource: Get;

where T: GetRelated<Rel>

In the long (long) term, this introduces a syntax which could open the door for ConstraintKinds-like "associated traits" as in:

trait Foo {
    trait Constraint;
    fn foo<T: Self::Constraint>(&self, arg: T);
}

impl Foo for u32 {
    trait Constraint = Display;
    fn foo<T: Display>(&self, arg: T) { ... }
}

There's a lot of other questions around a feature like that, and I don't want to conflate this RFC with that idea, but I just want to note it as a potential extension.

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Dec 8, 2016

Contributor

Hey, let's try to move this along!

Concerns raised in the discussion so far (let me know if I missed any or got anything wrong):

  • Syntax
    • There seems to be general agreement that it should be trait X = Y; not trait X as Y;
    • For full generality we need an optional where clause, i.e. trait X = Y where Self: Z
  • Semantics
    • Can we make trait aliases to combinations of traits, i.e. trait X = Y + Z?
    • Can we make an alias to an HRTB? Maybe we should ban it because it makes it too easy to hide lifetimes that users need to know about.
    • Is an alias really an alias or a new trait? That is, given trait X = Y; can I cast between &X and &Y? Similarly, can I write impl X for Struct { ... }? These things seem desirable, but they can only work for "simple" aliases, not ones with where clauses, HRTB or combinations of traits.

To resolve the two conflicting semantic points, there are three choices:

  1. Restrict to "simple" aliases: no where, for or +
  2. Fancy features (casting and impling) only available for "simple" aliases, because other aliases are actually new traits
  3. Fancy features not supported (yet?) for aliases, because they are all new traits

(1) seems inadequate, we would still need the trick from #1733 (comment) in many cases. (2) creates an inconsistency, but lets us write more code than (3). I read @withoutboats as favoring (3), and I think I do too (we can always add fancyness later).

Let's get to FCP! 🚀

Contributor

durka commented Dec 8, 2016

Hey, let's try to move this along!

Concerns raised in the discussion so far (let me know if I missed any or got anything wrong):

  • Syntax
    • There seems to be general agreement that it should be trait X = Y; not trait X as Y;
    • For full generality we need an optional where clause, i.e. trait X = Y where Self: Z
  • Semantics
    • Can we make trait aliases to combinations of traits, i.e. trait X = Y + Z?
    • Can we make an alias to an HRTB? Maybe we should ban it because it makes it too easy to hide lifetimes that users need to know about.
    • Is an alias really an alias or a new trait? That is, given trait X = Y; can I cast between &X and &Y? Similarly, can I write impl X for Struct { ... }? These things seem desirable, but they can only work for "simple" aliases, not ones with where clauses, HRTB or combinations of traits.

To resolve the two conflicting semantic points, there are three choices:

  1. Restrict to "simple" aliases: no where, for or +
  2. Fancy features (casting and impling) only available for "simple" aliases, because other aliases are actually new traits
  3. Fancy features not supported (yet?) for aliases, because they are all new traits

(1) seems inadequate, we would still need the trick from #1733 (comment) in many cases. (2) creates an inconsistency, but lets us write more code than (3). I read @withoutboats as favoring (3), and I think I do too (we can always add fancyness later).

Let's get to FCP! 🚀

@glaebhoerl

This comment has been minimized.

Show comment
Hide comment
@glaebhoerl

glaebhoerl Dec 8, 2016

Contributor

I think a trait alias should definitely be "just" an alias, not a new trait. Given trait X = Y, you don't need to cast between &X and &Y because they are the same type. For impls, I think either of disallowing trait aliases entirely, or only disallowing the ones which it wouldn't make sense to write directly would be fine.

Contributor

glaebhoerl commented Dec 8, 2016

I think a trait alias should definitely be "just" an alias, not a new trait. Given trait X = Y, you don't need to cast between &X and &Y because they are the same type. For impls, I think either of disallowing trait aliases entirely, or only disallowing the ones which it wouldn't make sense to write directly would be fine.

@solson

This comment has been minimized.

Show comment
Hide comment
@solson

solson Dec 8, 2016

Member

For full generality we need an optional where clause, i.e. trait X = Y where Self: Z

@durka Full generality is a bit worse than that, since we could have where clause aliases that don't involve a trait bound, so the trait is optional, too, or something...

disallowing the ones which it wouldn't make sense to write directly

@glaebhoerl This seems reasonable. It also applies when using trait aliases as trait object types.

Member

solson commented Dec 8, 2016

For full generality we need an optional where clause, i.e. trait X = Y where Self: Z

@durka Full generality is a bit worse than that, since we could have where clause aliases that don't involve a trait bound, so the trait is optional, too, or something...

disallowing the ones which it wouldn't make sense to write directly

@glaebhoerl This seems reasonable. It also applies when using trait aliases as trait object types.

@solson

This comment has been minimized.

Show comment
Hide comment
@solson

solson Dec 8, 2016

Member

There are some other interesting points with associated types:

trait Foo = Iterator<Item = i32>;
trait Bar = Iterator;

I can make a trait object &Foo, but can I make the trait object &Bar<Item = i32>? I'm leaning towards no for simplicity's sake, but it might be reasonable. You can get around it with trait Baz<T> = Iterator<Item = T>.

I can impl Bar for T { type Item = i32; ... }, but can I impl Foo for T { ... } with the Item implied? I would say no, since if you write it directly, you get the error associated type bindings are not allowed here.

Member

solson commented Dec 8, 2016

There are some other interesting points with associated types:

trait Foo = Iterator<Item = i32>;
trait Bar = Iterator;

I can make a trait object &Foo, but can I make the trait object &Bar<Item = i32>? I'm leaning towards no for simplicity's sake, but it might be reasonable. You can get around it with trait Baz<T> = Iterator<Item = T>.

I can impl Bar for T { type Item = i32; ... }, but can I impl Foo for T { ... } with the Item implied? I would say no, since if you write it directly, you get the error associated type bindings are not allowed here.

@phaazon

This comment has been minimized.

Show comment
Hide comment
@phaazon

phaazon Dec 8, 2016

Contributor

I think a trait alias should definitely be "just" an alias, not a new trait.

Yes, definitely – see my initial example for the motivation, being able to use a trait Trait as Trait<Item=Something>. Having a false alias is already kind of supported:

trait FooAlias: Foo { }

FooAlias is not really an alias of FooFoo is just a superclass. Aliasing implies substitution, exactly like type does.

About impl, I guess we should restrict impl to “true” traits and discard implementing trait aliases for the type family reason exposed by @solson in their last paragraph.

What are the next steps to move forward?

Contributor

phaazon commented Dec 8, 2016

I think a trait alias should definitely be "just" an alias, not a new trait.

Yes, definitely – see my initial example for the motivation, being able to use a trait Trait as Trait<Item=Something>. Having a false alias is already kind of supported:

trait FooAlias: Foo { }

FooAlias is not really an alias of FooFoo is just a superclass. Aliasing implies substitution, exactly like type does.

About impl, I guess we should restrict impl to “true” traits and discard implementing trait aliases for the type family reason exposed by @solson in their last paragraph.

What are the next steps to move forward?

phaazon added some commits Apr 13, 2017

Merge pull request #3 from pnkfelix/add-parametric-trait-alias-examples
include explicit examples of parametric aliases.
Merge pull request #4 from pnkfelix/alternatives-to-equals-where
Explicitly point out alternatives to `trait Alias = where PREDICATES;`
Merge pull request #5 from pnkfelix/mult-unbound-assoc-types-with-sam…
…e-name

Add section pointing out how associated item ambiguity is handled.
@vitiral

This comment has been minimized.

Show comment
Hide comment
@vitiral

vitiral Apr 14, 2017

I just want to throw out there that we already have an "aliasing" keyword, and that is type. I know this is mentioned in the RFC, but the only argument seems to be:

However, this mixes the concepts of types and traits, which are different, and allows nonsense like type Foo = Rc + f32; to parse.

It allows it to parse... but doesn't the compiler know the difference? Lots of things "parse" that aren't valid.

I would prefer we use type because it makes it easier to learn. It would make it so that there is only one "aliasing" keyword, and that is type -- and that is the only thing type is used for, rather than trait being used for now 3 things -- declaring a trait, aliasing a trait, and returning a trait from a function.

Also, maybe I'm just too new to the language but I'm not sure what the difference between "type" and "trait" are in this context -- I thought type is literally just an alias in rust? If that is the case, then wouldn't a trait alias be pretty much the same thing as a type alias?

vitiral commented Apr 14, 2017

I just want to throw out there that we already have an "aliasing" keyword, and that is type. I know this is mentioned in the RFC, but the only argument seems to be:

However, this mixes the concepts of types and traits, which are different, and allows nonsense like type Foo = Rc + f32; to parse.

It allows it to parse... but doesn't the compiler know the difference? Lots of things "parse" that aren't valid.

I would prefer we use type because it makes it easier to learn. It would make it so that there is only one "aliasing" keyword, and that is type -- and that is the only thing type is used for, rather than trait being used for now 3 things -- declaring a trait, aliasing a trait, and returning a trait from a function.

Also, maybe I'm just too new to the language but I'm not sure what the difference between "type" and "trait" are in this context -- I thought type is literally just an alias in rust? If that is the case, then wouldn't a trait alias be pretty much the same thing as a type alias?

@eddyb

This comment has been minimized.

Show comment
Hide comment
@eddyb

eddyb Apr 14, 2017

Member

@vitiral No, type is not a general-purpose aliasing keyword. It's specifically for types.
pub use is general-purpose. But type is for types and const is for constant values etc.

Traits are not types and trait objects having the same syntax as paths to the traits themselves is being regarded more and more as a mistake that creates confusion and prevents keywordless impl Trait.

and returning a trait from a function

Are you talking about -> impl Trait? Because that's impl not trait.

Member

eddyb commented Apr 14, 2017

@vitiral No, type is not a general-purpose aliasing keyword. It's specifically for types.
pub use is general-purpose. But type is for types and const is for constant values etc.

Traits are not types and trait objects having the same syntax as paths to the traits themselves is being regarded more and more as a mistake that creates confusion and prevents keywordless impl Trait.

and returning a trait from a function

Are you talking about -> impl Trait? Because that's impl not trait.

Merge pull request #6 from pnkfelix/associated-type-rebinding
It is an error to incompatibly override an equivalence constraint.
@rfcbot

This comment has been minimized.

Show comment
Hide comment
@rfcbot

rfcbot Apr 23, 2017

The final comment period is now complete.

rfcbot commented Apr 23, 2017

The final comment period is now complete.

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Apr 24, 2017

Contributor

I want to learn how to merge an RFC, so I'm leaving this note here that if anyone comes by to merge this they should instead write down the procedure and I'll do it.

Contributor

withoutboats commented Apr 24, 2017

I want to learn how to merge an RFC, so I'm leaving this note here that if anyone comes by to merge this they should instead write down the procedure and I'll do it.

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Apr 24, 2017

Member

@withoutboats what I've always done is, checked out the PR locally, added a commit that moves the file name from 0000- to its RFC number, and then pushed to master directly. Not sure if the language team has some other kind of process.

Member

steveklabnik commented Apr 24, 2017

@withoutboats what I've always done is, checked out the PR locally, added a commit that moves the file name from 0000- to its RFC number, and then pushed to master directly. Not sure if the language team has some other kind of process.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Apr 24, 2017

Contributor

@steveklabnik sadly, that is not sufficient. There are a few more steps:

  • Open a tracking issue over on rust-lang/rust. I would include:
    • A checklist with initial entries - [ ] Implement, - [ ] Document, and - [ ] Stabilize
    • The labels B-rfc-approved, B-unstable, and T-lang
    • a checklist for any unresolved questions found in the RFC, to ensure they are not forgotten
    • might as well cc @rust-lang/lang too :)
  • Edit the RFC text to include a link to the tracking issue as well as the PR.
  • Leave a comment here directing people with further questions over to the tracking issue (something like this).

We ought to put these steps over in the forge or something. And we need a nice template for tracking issues.

Contributor

nikomatsakis commented Apr 24, 2017

@steveklabnik sadly, that is not sufficient. There are a few more steps:

  • Open a tracking issue over on rust-lang/rust. I would include:
    • A checklist with initial entries - [ ] Implement, - [ ] Document, and - [ ] Stabilize
    • The labels B-rfc-approved, B-unstable, and T-lang
    • a checklist for any unresolved questions found in the RFC, to ensure they are not forgotten
    • might as well cc @rust-lang/lang too :)
  • Edit the RFC text to include a link to the tracking issue as well as the PR.
  • Leave a comment here directing people with further questions over to the tracking issue (something like this).

We ought to put these steps over in the forge or something. And we need a nice template for tracking issues.

@kennytm

This comment has been minimized.

Show comment
Hide comment
@kennytm

kennytm Apr 24, 2017

Member

@nikomatsakis I think all of these should be managed by rfc-bot 😉

Member

kennytm commented Apr 24, 2017

@nikomatsakis I think all of these should be managed by rfc-bot 😉

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Apr 24, 2017

Contributor

@kennytm I agree! =)

Contributor

nikomatsakis commented Apr 24, 2017

@kennytm I agree! =)

@withoutboats withoutboats referenced this pull request Apr 24, 2017

Open

Tracking issue for trait aliases #41517

0 of 3 tasks complete

@withoutboats withoutboats merged commit 2a1a5b2 into rust-lang:master Apr 24, 2017

@withoutboats

This comment has been minimized.

Show comment
Hide comment
@withoutboats

withoutboats Apr 24, 2017

Contributor

Thanks for the RFC @phaazon, I know it went through a lot of revision from start to finish. :) I keep finding places in my code where I want to use this feature.

For any further discussion of this feature go to the tracking issue here: rust-lang/rust#41517

Contributor

withoutboats commented Apr 24, 2017

Thanks for the RFC @phaazon, I know it went through a lot of revision from start to finish. :) I keep finding places in my code where I want to use this feature.

For any further discussion of this feature go to the tracking issue here: rust-lang/rust#41517

@steveklabnik

This comment has been minimized.

Show comment
Hide comment
@steveklabnik

steveklabnik Apr 25, 2017

Member

We ought to put these steps over in the forge or something. And we need a nice template for tracking issues.

👍 ; I missed these things because doc RFCs tend to be about conventions, and don't have tracking issues. 😓

Member

steveklabnik commented Apr 25, 2017

We ought to put these steps over in the forge or something. And we need a nice template for tracking issues.

👍 ; I missed these things because doc RFCs tend to be about conventions, and don't have tracking issues. 😓

@phaazon phaazon deleted the phaazon:trait-alias branch Apr 25, 2017

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Apr 25, 2017

Contributor

Create a description of the process here: rust-lang-nursery/rust-forge#54

Contributor

nikomatsakis commented Apr 25, 2017

Create a description of the process here: rust-lang-nursery/rust-forge#54

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 15, 2017

Heap histogram - WIP
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 16, 2017

Heap histogram - WIP
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 16, 2017

Heap histogram - WIP
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 16, 2017

Print heap histogram
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 16, 2017

Print heap histogram
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 16, 2017

Print heap histogram - WIP
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 16, 2017

Print heap histogram - WIP
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 26, 2017

Print heap histogram - WIP
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jun 29, 2017

Print heap histogram
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

glyn added a commit to cloudfoundry/jvmkill that referenced this pull request Jul 5, 2017

Print heap histogram
Also:
* Avoid unhelpful compiler warnings
* Capture common code in a macro

Note: it would have been nice to alias the closure type, but
rust-lang/rfcs#1733 is not yet implemented and macros
can't cope (rust-lang/rust#24010).

@durka durka referenced this pull request Oct 16, 2017

Merged

trait alias infrastructure #45047

7 of 10 tasks complete

@comex comex referenced this pull request Oct 27, 2017

Open

Associated traits #2190

@clarcharr

This comment has been minimized.

Show comment
Hide comment
@clarcharr

clarcharr Feb 27, 2018

Contributor

Haven't read all the comments, but by the existing grammar is trait Trait = ; allowed?

Contributor

clarcharr commented Feb 27, 2018

Haven't read all the comments, but by the existing grammar is trait Trait = ; allowed?

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Feb 27, 2018

Contributor
Contributor

durka commented Feb 27, 2018

@cramertj

This comment has been minimized.

Show comment
Hide comment
@cramertj

cramertj Feb 27, 2018

Member

I'd have guessed it would be a bound which accepted all types. If that works, I'd also expect trait Static = 'static; to be a bound which accepted all 'static types.

Member

cramertj commented Feb 27, 2018

I'd have guessed it would be a bound which accepted all types. If that works, I'd also expect trait Static = 'static; to be a bound which accepted all 'static types.

@durka

This comment has been minimized.

Show comment
Hide comment
@durka

durka Feb 27, 2018

Contributor
Contributor

durka commented Feb 27, 2018

@Boscop

This comment has been minimized.

Show comment
Hide comment
@Boscop

Boscop Mar 26, 2018

Would this also work?

trait Foo<'a> = Bar<'a> + 'a;

It should!

Boscop commented Mar 26, 2018

Would this also work?

trait Foo<'a> = Bar<'a> + 'a;

It should!

@phaazon

This comment has been minimized.

Show comment
Hide comment
@phaazon

phaazon Mar 27, 2018

Contributor

@Boscop The RFC enables it, yes.

Contributor

phaazon commented Mar 27, 2018

@Boscop The RFC enables it, yes.

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