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

Tracking issue for specialization (RFC 1210) #31844

Open
nikomatsakis opened this Issue Feb 23, 2016 · 133 comments

Comments

Projects
None yet
@nikomatsakis
Contributor

nikomatsakis commented Feb 23, 2016

This is a tracking issue for specialization (rust-lang/rfcs#1210).

Major implementation steps:

  • Land #30652 =)
  • Restrictions around lifetime dispatch (currently a soundness hole)
  • default impl (#37653)
  • Integration with associated consts
  • Bounds not always properly enforced (#33017)
  • Should we permit empty impls if parent has no default members? #48444
  • implement "always applicable" impls #48538

Unresolved questions from the RFC:

  • Should associated type be specializable at all?
  • When should projection reveal a default type? Never during typeck? Or when monomorphic?
  • Should default trait items be considered default (i.e. specializable)?
  • Should we have default impl (where all items are default) or partial impl (where default is opt-in)
  • How should we deal with lifetime dispatchability?
@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Feb 24, 2016

Member

Some additional open questions:

  • Should we revisit the orphan rules in the light of specialization? Are there ways to make things more flexible now?
  • Should we extend the "chain rule" in the RFC to something more expressive, like the so-called "lattice rule"?
  • Related to both of the above, how does negative reasoning fit into the story? Can we recover the negative reasoning we need by a clever enough use of specialization/orphan rules, or should we make it more first-class?
Member

aturon commented Feb 24, 2016

Some additional open questions:

  • Should we revisit the orphan rules in the light of specialization? Are there ways to make things more flexible now?
  • Should we extend the "chain rule" in the RFC to something more expressive, like the so-called "lattice rule"?
  • Related to both of the above, how does negative reasoning fit into the story? Can we recover the negative reasoning we need by a clever enough use of specialization/orphan rules, or should we make it more first-class?
@arielb1

This comment has been minimized.

Show comment
Hide comment
@arielb1

arielb1 Feb 24, 2016

Contributor

I am not sure that specialization changes the orphan rules:

  • The "linking" orphan rules must stay the same, because otherwise you would not have safe linking.
  • I don't think the "future compatibility" orphan rules should change. Adding a non-specializable impl under you would still be a breaking change.

Worse than that, the "future compatibility" orphan rules keep cross-crate specialization under pretty heavy control. Without them, default-impls leaving their methods open becomes much worse.

I never liked explicit negative reasoning. I think the total negative reasoning specialization provides is a nice compromise.

Contributor

arielb1 commented Feb 24, 2016

I am not sure that specialization changes the orphan rules:

  • The "linking" orphan rules must stay the same, because otherwise you would not have safe linking.
  • I don't think the "future compatibility" orphan rules should change. Adding a non-specializable impl under you would still be a breaking change.

Worse than that, the "future compatibility" orphan rules keep cross-crate specialization under pretty heavy control. Without them, default-impls leaving their methods open becomes much worse.

I never liked explicit negative reasoning. I think the total negative reasoning specialization provides is a nice compromise.

@sgrif

This comment has been minimized.

Show comment
Hide comment
@sgrif

sgrif Mar 20, 2016

Contributor

Should this impl be allowed with specialization as implemented? Or am I missing something?
http://is.gd/3Ul0pe

Contributor

sgrif commented Mar 20, 2016

Should this impl be allowed with specialization as implemented? Or am I missing something?
http://is.gd/3Ul0pe

@sgrif

This comment has been minimized.

Show comment
Hide comment
@sgrif

sgrif Mar 20, 2016

Contributor

Same with this one, would have expected it to compile: http://is.gd/RyFIEl

Contributor

sgrif commented Mar 20, 2016

Same with this one, would have expected it to compile: http://is.gd/RyFIEl

@sgrif

This comment has been minimized.

Show comment
Hide comment
@sgrif

sgrif Mar 20, 2016

Contributor

Looks like there's some quirks in determining overlap when associated types are involved. This compiles: http://is.gd/JBPzIX, while this effectively identical code doesn't: http://is.gd/0ksLPX

Contributor

sgrif commented Mar 20, 2016

Looks like there's some quirks in determining overlap when associated types are involved. This compiles: http://is.gd/JBPzIX, while this effectively identical code doesn't: http://is.gd/0ksLPX

@SergioBenitez

This comment has been minimized.

Show comment
Hide comment
@SergioBenitez

SergioBenitez Mar 23, 2016

Contributor

Here's a piece of code I expected to compile with specialization:

http://is.gd/3BNbfK

#![feature(specialization)]

use std::str::FromStr;

struct Error;

trait Simple<'a> {
    fn do_something(s: &'a str) -> Result<Self, Error>;
}

impl<'a> Simple<'a> for &'a str {
     fn do_something(s: &'a str) -> Result<Self, Error> {
        Ok(s)
    }
}

impl<'a, T: FromStr> Simple<'a> for T {
    fn do_something(s: &'a str) -> Result<Self, Error> {
        T::from_str(s).map_err(|_| Error)
    }
}

fn main() {
    // Do nothing. Just type check.
}

Compilation fails with the compiler citing implementation conflicts. Note that &str doesn't implement FromStr, so there shouldn't be a conflict.

Contributor

SergioBenitez commented Mar 23, 2016

Here's a piece of code I expected to compile with specialization:

http://is.gd/3BNbfK

#![feature(specialization)]

use std::str::FromStr;

struct Error;

trait Simple<'a> {
    fn do_something(s: &'a str) -> Result<Self, Error>;
}

impl<'a> Simple<'a> for &'a str {
     fn do_something(s: &'a str) -> Result<Self, Error> {
        Ok(s)
    }
}

impl<'a, T: FromStr> Simple<'a> for T {
    fn do_something(s: &'a str) -> Result<Self, Error> {
        T::from_str(s).map_err(|_| Error)
    }
}

fn main() {
    // Do nothing. Just type check.
}

Compilation fails with the compiler citing implementation conflicts. Note that &str doesn't implement FromStr, so there shouldn't be a conflict.

SergioBenitez added a commit to SergioBenitez/Rocket that referenced this issue Mar 23, 2016

Now support Result responses.
Experimented with the new impl specialization features of Rust. They work! But
they're not quite there yet. Specifically, I was able to specialize on
`Responder`, but when trying to remove the macro in `FromParam`, it didn't work.
See rust-lang/rust#31844.
@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Mar 23, 2016

Member

@sgrif

I had time to look at the first two examples. Here are my notes.

Example 1

First case, you have:

  • FromSqlRow<ST, DB> for T where T: FromSql<ST, DB>
  • FromSqlRow<(ST, SU), DB> for (T, U) where T: FromSqlRow<ST, DB>, U: FromSqlRow<SU, DB>,

The problem is that these impls overlap but neither is more specific than the other:

  • You can potentially have a T: FromSql<ST, DB> where T is not a pair (so it matches the first impl but not the second).
  • You can potentially have a (T, U) where:
    • T: FromSqlRow<ST, DB>,
    • U: FromSqlRow<SU, DB>, but not
    • (T, U): FromSql<(ST, SU), DB>
    • (so the second impl matches, but not the first)
  • The two impls overlap because you can have a (T, U) such that:
    • T: FromSqlRow<ST, DB>
    • U: FromSqlRow<SU, DB>
    • (T, U): FromSql<(ST, SU), DB>

This is the kind of situation that lattice impls would allow -- you'd have to write a third impl for the overlapping case, and say what it should do. Alternatively, negative trait impls might give you a way to rule out overlap or otherwise tweak which matches are possible.

Example 2

You have:

  • Queryable<ST, DB> for T where T: FromSqlRow<ST, DB>
  • Queryable<Nullable<ST>, DB> for Option<T> where T: Queryable<ST, DB>

These overlap because you can have Option<T> where:

  • T: Queryable<ST, DB>
  • Option<T>: FromSqlRow<Nullable<ST>, DB>

But neither impl is more specific:

  • You can have a T such that T: FromSqlRow<ST, DB> but T is not an Option<U> (matches first impl but not second)
  • You can have an Option<T> such that T: Queryable<ST, DB> but not Option<T>: FromSqlRow<Nullable<ST>, DB>
Member

aturon commented Mar 23, 2016

@sgrif

I had time to look at the first two examples. Here are my notes.

Example 1

First case, you have:

  • FromSqlRow<ST, DB> for T where T: FromSql<ST, DB>
  • FromSqlRow<(ST, SU), DB> for (T, U) where T: FromSqlRow<ST, DB>, U: FromSqlRow<SU, DB>,

The problem is that these impls overlap but neither is more specific than the other:

  • You can potentially have a T: FromSql<ST, DB> where T is not a pair (so it matches the first impl but not the second).
  • You can potentially have a (T, U) where:
    • T: FromSqlRow<ST, DB>,
    • U: FromSqlRow<SU, DB>, but not
    • (T, U): FromSql<(ST, SU), DB>
    • (so the second impl matches, but not the first)
  • The two impls overlap because you can have a (T, U) such that:
    • T: FromSqlRow<ST, DB>
    • U: FromSqlRow<SU, DB>
    • (T, U): FromSql<(ST, SU), DB>

This is the kind of situation that lattice impls would allow -- you'd have to write a third impl for the overlapping case, and say what it should do. Alternatively, negative trait impls might give you a way to rule out overlap or otherwise tweak which matches are possible.

Example 2

You have:

  • Queryable<ST, DB> for T where T: FromSqlRow<ST, DB>
  • Queryable<Nullable<ST>, DB> for Option<T> where T: Queryable<ST, DB>

These overlap because you can have Option<T> where:

  • T: Queryable<ST, DB>
  • Option<T>: FromSqlRow<Nullable<ST>, DB>

But neither impl is more specific:

  • You can have a T such that T: FromSqlRow<ST, DB> but T is not an Option<U> (matches first impl but not second)
  • You can have an Option<T> such that T: Queryable<ST, DB> but not Option<T>: FromSqlRow<Nullable<ST>, DB>
@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon Mar 23, 2016

Member

@SergioBenitez

Compilation fails with the compiler citing implementation conflicts. Note that &str doesn't implement FromStr, so there shouldn't be a conflict.

The problem is that the compiler is conservatively assuming that &str might come to implement FromStr in the future. That may seem silly for this example, but in general, we add new impls all the time, and we want to protect downstream code from breaking when we add those impls.

This is a conservative choice, and is something we might want to relax over time. You can get the background here:

Member

aturon commented Mar 23, 2016

@SergioBenitez

Compilation fails with the compiler citing implementation conflicts. Note that &str doesn't implement FromStr, so there shouldn't be a conflict.

The problem is that the compiler is conservatively assuming that &str might come to implement FromStr in the future. That may seem silly for this example, but in general, we add new impls all the time, and we want to protect downstream code from breaking when we add those impls.

This is a conservative choice, and is something we might want to relax over time. You can get the background here:

@sgrif

This comment has been minimized.

Show comment
Hide comment
@sgrif

sgrif Mar 23, 2016

Contributor

Thank you for clarifying those two cases. It makes complete sense now

On Tue, Mar 22, 2016, 6:34 PM Aaron Turon notifications@github.com wrote:

@SergioBenitez https://github.com/SergioBenitez

Compilation fails with the compiler citing implementation conflicts. Note
that &str doesn't implement FromStr, so there shouldn't be a conflict.

The problem is that the compiler is conservatively assuming that &str
might come to implement FromStr in the future. That may seem silly for
this example, but in general, we add new impls all the time, and we want to
protect downstream code from breaking when we add those impls.

This is a conservative choice, and is something we might want to relax
over time. You can get the background here:

http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#31844 (comment)

Contributor

sgrif commented Mar 23, 2016

Thank you for clarifying those two cases. It makes complete sense now

On Tue, Mar 22, 2016, 6:34 PM Aaron Turon notifications@github.com wrote:

@SergioBenitez https://github.com/SergioBenitez

Compilation fails with the compiler citing implementation conflicts. Note
that &str doesn't implement FromStr, so there shouldn't be a conflict.

The problem is that the compiler is conservatively assuming that &str
might come to implement FromStr in the future. That may seem silly for
this example, but in general, we add new impls all the time, and we want to
protect downstream code from breaking when we add those impls.

This is a conservative choice, and is something we might want to relax
over time. You can get the background here:

http://smallcultfollowing.com/babysteps/blog/2015/01/14/little-orphan-impls/


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#31844 (comment)

@SergioBenitez

This comment has been minimized.

Show comment
Hide comment
@SergioBenitez

SergioBenitez Mar 23, 2016

Contributor

@aturon

The problem is that the compiler is conservatively assuming that &str might come to implement FromStr in the future. That may seem silly for this example, but in general, we add new impls all the time, and we want to protect downstream code from breaking when we add those impls.

Isn't this exactly what specialization is trying to address? With specialization, I would expect that even if an implementation of FromStr for &str were added in the future, the direct implementation of the Simple trait for &str would take precedence.

Contributor

SergioBenitez commented Mar 23, 2016

@aturon

The problem is that the compiler is conservatively assuming that &str might come to implement FromStr in the future. That may seem silly for this example, but in general, we add new impls all the time, and we want to protect downstream code from breaking when we add those impls.

Isn't this exactly what specialization is trying to address? With specialization, I would expect that even if an implementation of FromStr for &str were added in the future, the direct implementation of the Simple trait for &str would take precedence.

@sgrif

This comment has been minimized.

Show comment
Hide comment
@sgrif

sgrif Mar 23, 2016

Contributor

@SergioBenitez you need to put default fn in the more general impl. Your
example isn't specializable.

On Tue, Mar 22, 2016, 6:54 PM Sergio Benitez notifications@github.com
wrote:

@aturon https://github.com/aturon

The problem is that the compiler is conservatively assuming that &str
might come to implement FromStr in the future. That may seem silly for this
example, but in general, we add new impls all the time, and we want to
protect downstream code from breaking when we add those impls.

Isn't this exactly what specialization is trying to address? With
specialization, I would expect that even if an implementation of FromStr
for &str were added in the future, the direct implementation for the
trait for &str would take precedence.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#31844 (comment)

Contributor

sgrif commented Mar 23, 2016

@SergioBenitez you need to put default fn in the more general impl. Your
example isn't specializable.

On Tue, Mar 22, 2016, 6:54 PM Sergio Benitez notifications@github.com
wrote:

@aturon https://github.com/aturon

The problem is that the compiler is conservatively assuming that &str
might come to implement FromStr in the future. That may seem silly for this
example, but in general, we add new impls all the time, and we want to
protect downstream code from breaking when we add those impls.

Isn't this exactly what specialization is trying to address? With
specialization, I would expect that even if an implementation of FromStr
for &str were added in the future, the direct implementation for the
trait for &str would take precedence.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#31844 (comment)

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges Apr 1, 2016

I think "default" trait items being automatically considered default sounds confusing. You might want both parametricity for a trait like in Haskell, etc. along side with easing the impls. Also you cannot easily grep for them like you can for default. It's not hard to both type the default keyword and give a default implementation, but they cannot be separated as is. Also, if one wants to clarify the language, then these "default" trait items could be renamed to "trait proposed" items in documentation.

burdges commented Apr 1, 2016

I think "default" trait items being automatically considered default sounds confusing. You might want both parametricity for a trait like in Haskell, etc. along side with easing the impls. Also you cannot easily grep for them like you can for default. It's not hard to both type the default keyword and give a default implementation, but they cannot be separated as is. Also, if one wants to clarify the language, then these "default" trait items could be renamed to "trait proposed" items in documentation.

@Stebalien

This comment has been minimized.

Show comment
Hide comment
@Stebalien

Stebalien Apr 15, 2016

Contributor

Note from #32999 (comment): if we do go with the lattice rule (or allow negative constraints), the "use an intermediate trait" trick to prevent further specialization of something will no longer work.

Contributor

Stebalien commented Apr 15, 2016

Note from #32999 (comment): if we do go with the lattice rule (or allow negative constraints), the "use an intermediate trait" trick to prevent further specialization of something will no longer work.

@arielb1

This comment has been minimized.

Show comment
Hide comment
@arielb1

arielb1 Apr 15, 2016

Contributor

@Stebalien

Why won't it work? The trick limits the specialization to a private trait. You can't specialize the private trait if you can't access it.

Contributor

arielb1 commented Apr 15, 2016

@Stebalien

Why won't it work? The trick limits the specialization to a private trait. You can't specialize the private trait if you can't access it.

@Stebalien

This comment has been minimized.

Show comment
Hide comment
@Stebalien

Stebalien Apr 15, 2016

Contributor

@arielb1 Ah. Good point. In my case, the trait isn't private.

Contributor

Stebalien commented Apr 15, 2016

@arielb1 Ah. Good point. In my case, the trait isn't private.

@arielb1

This comment has been minimized.

Show comment
Hide comment
@arielb1

arielb1 Apr 15, 2016

Contributor

I don't think the "externals can't specialize because orphan forward-compatibility + coherence rulea" reasoning is particularly interesting or useful. Especially when we don't commit to our specific coherence rules.

Contributor

arielb1 commented Apr 15, 2016

I don't think the "externals can't specialize because orphan forward-compatibility + coherence rulea" reasoning is particularly interesting or useful. Especially when we don't commit to our specific coherence rules.

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges May 7, 2016

Is there a way to access an overridden default impl? If so, this could aid in constructing tests. See Design By Contract and libhoare.

burdges commented May 7, 2016

Is there a way to access an overridden default impl? If so, this could aid in constructing tests. See Design By Contract and libhoare.

@rphmeier

This comment has been minimized.

Show comment
Hide comment
@rphmeier

rphmeier May 7, 2016

Contributor

Allowing projection of default associated types during type-checking will allow enforcing type inequality at compile-time: https://gist.github.com/7c081574958d22f89d434a97b626b1e4

#![feature(specialization)]

pub trait NotSame {}

pub struct True;
pub struct False;

pub trait Sameness {
    type Same;
}

mod internal {
    pub trait PrivSameness {
        type Same;
    }
}

use internal::PrivSameness;

impl<A, B> Sameness for (A, B) {
    type Same = <Self as PrivSameness>::Same;
}

impl<A, B> PrivSameness for (A, B) {
    default type Same = False;
}
impl<A> PrivSameness for (A, A) {
    type Same = True;
}

impl<A, B> NotSame for (A, B) where (A, B): Sameness<Same=False> {}

fn not_same<A, B>() where (A, B): NotSame {}

fn main() {
    // would compile
    not_same::<i32, f32>();

    // would not compile
    // not_same::<i32, i32>();
}

edited per @burdges' comment

Contributor

rphmeier commented May 7, 2016

Allowing projection of default associated types during type-checking will allow enforcing type inequality at compile-time: https://gist.github.com/7c081574958d22f89d434a97b626b1e4

#![feature(specialization)]

pub trait NotSame {}

pub struct True;
pub struct False;

pub trait Sameness {
    type Same;
}

mod internal {
    pub trait PrivSameness {
        type Same;
    }
}

use internal::PrivSameness;

impl<A, B> Sameness for (A, B) {
    type Same = <Self as PrivSameness>::Same;
}

impl<A, B> PrivSameness for (A, B) {
    default type Same = False;
}
impl<A> PrivSameness for (A, A) {
    type Same = True;
}

impl<A, B> NotSame for (A, B) where (A, B): Sameness<Same=False> {}

fn not_same<A, B>() where (A, B): NotSame {}

fn main() {
    // would compile
    not_same::<i32, f32>();

    // would not compile
    // not_same::<i32, i32>();
}

edited per @burdges' comment

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges May 7, 2016

Just fyi @rphmeier one should probably avoid is.gd because it does not resolve for Tor users due to using CloudFlare. GitHub works fine with full URLs. And play.rust-lang.org works fine over Tor.

burdges commented May 7, 2016

Just fyi @rphmeier one should probably avoid is.gd because it does not resolve for Tor users due to using CloudFlare. GitHub works fine with full URLs. And play.rust-lang.org works fine over Tor.

@SimonSapin

This comment has been minimized.

Show comment
Hide comment
@SimonSapin

SimonSapin May 7, 2016

Contributor

@burdges FWIW play.rust-lang.org itself uses is.gd for its "Shorten" button.

It can probably be changed, though: https://github.com/rust-lang/rust-playpen/blob/9777ef59b/static/web.js#L333

Contributor

SimonSapin commented May 7, 2016

@burdges FWIW play.rust-lang.org itself uses is.gd for its "Shorten" button.

It can probably be changed, though: https://github.com/rust-lang/rust-playpen/blob/9777ef59b/static/web.js#L333

@zitsen

This comment has been minimized.

Show comment
Hide comment
@zitsen

zitsen May 17, 2016

use like this(https://is.gd/Ux6FNs):

#![feature(specialization)]
pub trait Foo {}
pub trait Bar: Foo {}
pub trait Baz: Foo {}

pub trait Trait {
    type Item;
}

struct Staff<T> { }

impl<T: Foo> Trait for Staff<T> {
    default type Item = i32;
}

impl<T: Foo + Bar> Trait for Staff<T> {
    type Item = i64;
}

impl<T: Foo + Baz> Trait for Staff<T> {
    type Item = f64;
}

fn main() {
    let _ = Staff { };
}

Error :

error: conflicting implementations of trait `Trait` for type `Staff<_>`: [--explain E0119]
  --> <anon>:20:1
20 |> impl<T: Foo + Baz> Trait for Staff<T> {
   |> ^
note: conflicting implementation is here:
  --> <anon>:16:1
16 |> impl<T: Foo + Bar> Trait for Staff<T> {
   |> ^

error: aborting due to previous error

Does feture specialization support this, and is there any other kind of implementations currently?

zitsen commented May 17, 2016

use like this(https://is.gd/Ux6FNs):

#![feature(specialization)]
pub trait Foo {}
pub trait Bar: Foo {}
pub trait Baz: Foo {}

pub trait Trait {
    type Item;
}

struct Staff<T> { }

impl<T: Foo> Trait for Staff<T> {
    default type Item = i32;
}

impl<T: Foo + Bar> Trait for Staff<T> {
    type Item = i64;
}

impl<T: Foo + Baz> Trait for Staff<T> {
    type Item = f64;
}

fn main() {
    let _ = Staff { };
}

Error :

error: conflicting implementations of trait `Trait` for type `Staff<_>`: [--explain E0119]
  --> <anon>:20:1
20 |> impl<T: Foo + Baz> Trait for Staff<T> {
   |> ^
note: conflicting implementation is here:
  --> <anon>:16:1
16 |> impl<T: Foo + Bar> Trait for Staff<T> {
   |> ^

error: aborting due to previous error

Does feture specialization support this, and is there any other kind of implementations currently?

@aturon

This comment has been minimized.

Show comment
Hide comment
@aturon

aturon May 17, 2016

Member

@zitsen

These impls are not allowed by the current specialization design, because neither T: Foo + Bar nor T: Foo + Baz is more specialized than the other. That is, if you have some T: Foo + Bar + Baz, it's not clear which impl should "win".

We have some thoughts on a more expressive system that would allow you to also give an impl for T: Foo + Bar + Baz and thus disambiguate, but that hasn't been fully proposed yet.

Member

aturon commented May 17, 2016

@zitsen

These impls are not allowed by the current specialization design, because neither T: Foo + Bar nor T: Foo + Baz is more specialized than the other. That is, if you have some T: Foo + Bar + Baz, it's not clear which impl should "win".

We have some thoughts on a more expressive system that would allow you to also give an impl for T: Foo + Bar + Baz and thus disambiguate, but that hasn't been fully proposed yet.

@rphmeier

This comment has been minimized.

Show comment
Hide comment
@rphmeier

rphmeier May 17, 2016

Contributor

If negative trait bounds trait Baz: !Bar ever land, that could also be used with specialization to prove that the sets of types that implement Bar and those that implement Baz are distinct and individually specializable.

Contributor

rphmeier commented May 17, 2016

If negative trait bounds trait Baz: !Bar ever land, that could also be used with specialization to prove that the sets of types that implement Bar and those that implement Baz are distinct and individually specializable.

@zitsen

This comment has been minimized.

Show comment
Hide comment
@zitsen

zitsen May 18, 2016

Seems @rphmeier 's reply is what I exactly want, impls for T: Foo + Bar + Baz would also help.

Just ignore this, I still have something to do with my case, and always exciting for the specialization and other features landing.

Thanks @aturon @rphmeier .

zitsen commented May 18, 2016

Seems @rphmeier 's reply is what I exactly want, impls for T: Foo + Bar + Baz would also help.

Just ignore this, I still have something to do with my case, and always exciting for the specialization and other features landing.

Thanks @aturon @rphmeier .

@kylewlacy

This comment has been minimized.

Show comment
Hide comment
@kylewlacy

kylewlacy May 24, 2016

I've been playing around with specialization lately, and I came across this weird case:

#![feature(specialization)]

trait Marker {
    type Mark;
}

trait Foo { fn foo(&self); }

struct Fizz;

impl Marker for Fizz {
    type Mark = ();
}

impl Foo for Fizz {
    fn foo(&self) { println!("Fizz!"); }
}

impl<T> Foo for T
    where T: Marker, T::Mark: Foo
{
    default fn foo(&self) { println!("Has Foo marker!"); }
}

struct Buzz;

impl Marker for Buzz {
    type Mark = Fizz;
}

fn main() {
    Fizz.foo();
    Buzz.foo();
}

Compiler output:

error: conflicting implementations of trait `Foo` for type `Fizz`: [--explain E0119]
  --> <anon>:19:1
19 |> impl<T> Foo for T
   |> ^
note: conflicting implementation is here:
  --> <anon>:15:1
15 |> impl Foo for Fizz {
   |> ^

playpen

I believe that the above should compile, and there's two interesting variations that actually do work-as-intended:

  1. Removing the where T::Mark: Fizz bound:
impl<T> Foo for T
    where T: Marker //, T::Mark: Fizz
{
    // ...
}

playpen

  1. Adding a "trait bound alias":
trait FooMarker { }
impl<T> FooMarker for T where T: Marker, T::Mark: Foo { }

impl<T> Foo for T where T: FooMarker {
    // ...
}

playpen

(Which doesn't work if Marker is defined in a separate crate (!), see this example repo)

I also believe that this issue might be related to #20400 somehow

EDIT: I've opened an issue about this: #36587

kylewlacy commented May 24, 2016

I've been playing around with specialization lately, and I came across this weird case:

#![feature(specialization)]

trait Marker {
    type Mark;
}

trait Foo { fn foo(&self); }

struct Fizz;

impl Marker for Fizz {
    type Mark = ();
}

impl Foo for Fizz {
    fn foo(&self) { println!("Fizz!"); }
}

impl<T> Foo for T
    where T: Marker, T::Mark: Foo
{
    default fn foo(&self) { println!("Has Foo marker!"); }
}

struct Buzz;

impl Marker for Buzz {
    type Mark = Fizz;
}

fn main() {
    Fizz.foo();
    Buzz.foo();
}

Compiler output:

error: conflicting implementations of trait `Foo` for type `Fizz`: [--explain E0119]
  --> <anon>:19:1
19 |> impl<T> Foo for T
   |> ^
note: conflicting implementation is here:
  --> <anon>:15:1
15 |> impl Foo for Fizz {
   |> ^

playpen

I believe that the above should compile, and there's two interesting variations that actually do work-as-intended:

  1. Removing the where T::Mark: Fizz bound:
impl<T> Foo for T
    where T: Marker //, T::Mark: Fizz
{
    // ...
}

playpen

  1. Adding a "trait bound alias":
trait FooMarker { }
impl<T> FooMarker for T where T: Marker, T::Mark: Foo { }

impl<T> Foo for T where T: FooMarker {
    // ...
}

playpen

(Which doesn't work if Marker is defined in a separate crate (!), see this example repo)

I also believe that this issue might be related to #20400 somehow

EDIT: I've opened an issue about this: #36587

@tomaka

This comment has been minimized.

Show comment
Hide comment
@tomaka

tomaka Jul 2, 2016

Contributor

I'm encountering an issue with specialization. Not sure if it's an implementation problem or a problem in the way specialization is specified.

use std::vec::IntoIter as VecIntoIter;

pub trait ClonableIterator: Iterator {
    type ClonableIter;

    fn clonable(self) -> Self::ClonableIter;
}

impl<T> ClonableIterator for T where T: Iterator {
    default type ClonableIter = VecIntoIter<T::Item>;

    default fn clonable(self) -> VecIntoIter<T::Item> {
        self.collect::<Vec<_>>().into_iter()
    }
}

impl<T> ClonableIterator for T where T: Iterator + Clone {
    type ClonableIter = T;

    #[inline]
    fn clonable(self) -> T {
        self
    }
}

(playpen)
(by the way, it would be nice if this code eventually landed in the stdlib one day)

This code fails with:

error: method `clonable` has an incompatible type for trait:
 expected associated type,
    found struct `std::vec::IntoIter` [--explain E0053]
  --> <anon>:14:5
   |>
14 |>     default fn clonable(self) -> VecIntoIter<T::Item> {
   |>     ^

Changing the return value to Self::ClonableIter gives the following error:

error: mismatched types [--explain E0308]
  --> <anon>:15:9
   |>
15 |>         self.collect::<Vec<_>>().into_iter()
   |>         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::vec::IntoIter`
note: expected type `<T as ClonableIterator>::ClonableIter`
note:    found type `std::vec::IntoIter<<T as std::iter::Iterator>::Item>`

Apparently you can't refer to the concrete type of a defaulted associated type, which I find quite limiting.

Contributor

tomaka commented Jul 2, 2016

I'm encountering an issue with specialization. Not sure if it's an implementation problem or a problem in the way specialization is specified.

use std::vec::IntoIter as VecIntoIter;

pub trait ClonableIterator: Iterator {
    type ClonableIter;

    fn clonable(self) -> Self::ClonableIter;
}

impl<T> ClonableIterator for T where T: Iterator {
    default type ClonableIter = VecIntoIter<T::Item>;

    default fn clonable(self) -> VecIntoIter<T::Item> {
        self.collect::<Vec<_>>().into_iter()
    }
}

impl<T> ClonableIterator for T where T: Iterator + Clone {
    type ClonableIter = T;

    #[inline]
    fn clonable(self) -> T {
        self
    }
}

(playpen)
(by the way, it would be nice if this code eventually landed in the stdlib one day)

This code fails with:

error: method `clonable` has an incompatible type for trait:
 expected associated type,
    found struct `std::vec::IntoIter` [--explain E0053]
  --> <anon>:14:5
   |>
14 |>     default fn clonable(self) -> VecIntoIter<T::Item> {
   |>     ^

Changing the return value to Self::ClonableIter gives the following error:

error: mismatched types [--explain E0308]
  --> <anon>:15:9
   |>
15 |>         self.collect::<Vec<_>>().into_iter()
   |>         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected associated type, found struct `std::vec::IntoIter`
note: expected type `<T as ClonableIterator>::ClonableIter`
note:    found type `std::vec::IntoIter<<T as std::iter::Iterator>::Item>`

Apparently you can't refer to the concrete type of a defaulted associated type, which I find quite limiting.

@Aatch

This comment has been minimized.

Show comment
Hide comment
@Aatch

Aatch Jul 6, 2016

Contributor

@tomaka it should work, the RFC text has this:

impl<T> Example for T {
    default type Output = Box<T>;
    default fn generate(self) -> Box<T> { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> bool { self }
}

(https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#the-default-keyword)

Which seems similar enough to your case to be relevant.

Contributor

Aatch commented Jul 6, 2016

@tomaka it should work, the RFC text has this:

impl<T> Example for T {
    default type Output = Box<T>;
    default fn generate(self) -> Box<T> { Box::new(self) }
}

impl Example for bool {
    type Output = bool;
    fn generate(self) -> bool { self }
}

(https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md#the-default-keyword)

Which seems similar enough to your case to be relevant.

@rphmeier

This comment has been minimized.

Show comment
Hide comment
@rphmeier

rphmeier Jul 6, 2016

Contributor

@Aatch that example doesn't seem to compile with the intuitive definition for the example trait: https://play.rust-lang.org/?gist=97ff3c2f7f3e50bd3aef000dbfa2ca4e&version=nightly&backtrace=0

the specialization code explicitly disallows this -- see #33481, which I initially thought was an error but turned out to be a diagnostics issue. My PRs to improve the diagnostics here went unnoticed, and I haven't maintained them to the latest master for quite some time.

Contributor

rphmeier commented Jul 6, 2016

@Aatch that example doesn't seem to compile with the intuitive definition for the example trait: https://play.rust-lang.org/?gist=97ff3c2f7f3e50bd3aef000dbfa2ca4e&version=nightly&backtrace=0

the specialization code explicitly disallows this -- see #33481, which I initially thought was an error but turned out to be a diagnostics issue. My PRs to improve the diagnostics here went unnoticed, and I haven't maintained them to the latest master for quite some time.

@stjepang

This comment has been minimized.

Show comment
Hide comment
@stjepang

stjepang Jan 7, 2018

Contributor

Would specialization allow adding something like the following to libcore?

impl<T: Ord> Eq for T {}

impl<T: Ord> PartialEq for T {
    default fn eq(&self, other: &Self) -> bool {
        self.cmp(other) == Ordering::Equal
    }
}

impl<T: Ord> PartialOrd for T {
    default fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

This way you could implement Ord for your custom type and have Eq, PartialEq, and PartialOrd be automatically implemented.

Note that implementing Ord and simultaneously deriving PartialEq or PartialOrd is dangerous and can lead to very subtle bugs! With these default impls you would be less tempted to derive those traits, so the problem would be somewhat mitigated.


Alternatively, we modify derivation to take advantage of specialization. For example, writing #[derive(PartialOrd)] above struct Foo(String) could generate the following code:

impl PartialOrd for Foo {
    default fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl PartialOrd for Foo where Foo: Ord {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

This way the default impl gets used if Ord is not implemented. But if it is, then PartialOrd relies on Ord. Unfortunately, this doesn't compile: error[E0119]: conflicting implementations of trait `std::cmp::PartialOrd` for type `Foo`

Contributor

stjepang commented Jan 7, 2018

Would specialization allow adding something like the following to libcore?

impl<T: Ord> Eq for T {}

impl<T: Ord> PartialEq for T {
    default fn eq(&self, other: &Self) -> bool {
        self.cmp(other) == Ordering::Equal
    }
}

impl<T: Ord> PartialOrd for T {
    default fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

This way you could implement Ord for your custom type and have Eq, PartialEq, and PartialOrd be automatically implemented.

Note that implementing Ord and simultaneously deriving PartialEq or PartialOrd is dangerous and can lead to very subtle bugs! With these default impls you would be less tempted to derive those traits, so the problem would be somewhat mitigated.


Alternatively, we modify derivation to take advantage of specialization. For example, writing #[derive(PartialOrd)] above struct Foo(String) could generate the following code:

impl PartialOrd for Foo {
    default fn partial_cmp(&self, other: &Foo) -> Option<Ordering> {
        self.0.partial_cmp(&other.0)
    }
}

impl PartialOrd for Foo where Foo: Ord {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

This way the default impl gets used if Ord is not implemented. But if it is, then PartialOrd relies on Ord. Unfortunately, this doesn't compile: error[E0119]: conflicting implementations of trait `std::cmp::PartialOrd` for type `Foo`

@scottmcm

This comment has been minimized.

Show comment
Hide comment
@scottmcm

scottmcm Jan 7, 2018

Member

@stjepang I certainly hope the blankets like that can be added -- impl<T:Copy> Clone for T too.

Member

scottmcm commented Jan 7, 2018

@stjepang I certainly hope the blankets like that can be added -- impl<T:Copy> Clone for T too.

@jan-hudec

This comment has been minimized.

Show comment
Hide comment
@jan-hudec

jan-hudec Jan 9, 2018

I think

impl<T: Ord> PartialEq for T

should be

impl<T, U> PartialEq<U> for T where T : PartialOrd<U>

because PartialOrd requires PartialEq and can provide it too.

jan-hudec commented Jan 9, 2018

I think

impl<T: Ord> PartialEq for T

should be

impl<T, U> PartialEq<U> for T where T : PartialOrd<U>

because PartialOrd requires PartialEq and can provide it too.

@burdges

This comment has been minimized.

Show comment
Hide comment
@burdges

burdges Jan 16, 2018

Right now, one cannot really use associated types to constrain a specialization, both because they cannot be left unspecified and because they trigger uneeded recursion. See dhardy/rand#18 (comment)

burdges commented Jan 16, 2018

Right now, one cannot really use associated types to constrain a specialization, both because they cannot be left unspecified and because they trigger uneeded recursion. See dhardy/rand#18 (comment)

@Centril

This comment has been minimized.

Show comment
Hide comment
@Centril

Centril Jan 22, 2018

Contributor

Eventually, I'ld love to see what I'm calling specialization groups with the syntax proposed by @nikomatsakis here #31844 (comment) and independently by me. I'ld like to write an RFC on that proposal later when we're closer to stabilizing specialization.

Contributor

Centril commented Jan 22, 2018

Eventually, I'ld love to see what I'm calling specialization groups with the syntax proposed by @nikomatsakis here #31844 (comment) and independently by me. I'ld like to write an RFC on that proposal later when we're closer to stabilizing specialization.

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis

nikomatsakis Feb 25, 2018

Contributor

Just in case nobody saw it, this blog post covers a proposal to make specialization sound in the face of lifetime-based dispatch.

Contributor

nikomatsakis commented Feb 25, 2018

Just in case nobody saw it, this blog post covers a proposal to make specialization sound in the face of lifetime-based dispatch.

@earthengine

This comment has been minimized.

Show comment
Hide comment
@earthengine

earthengine Apr 16, 2018

As copy closures were already stablized in Beta, developers have more motivation to stabilizing on specialization now. The reason is that Fn and FnOnce + Clone represent two overlapping set of closures, and in many case we need to implement traits for both of them.

Just figure out that the wording of rfc 2132 seems to imply that there are only 5 types of closures:

  • FnOnce (a move closure with all captured variables being neither Copy nor Clone)
  • FnOnce + Clone (a move closure with all captured variables being Clone)
  • FnOnce + Copy + Clone (a move closure with all captured variables being Copy and so Clone)
  • FnMut + FnOnce (a non-move closure with mutated captured variables)
  • Fn + FnMut + FnOnce + Copy + Clone (a non-move closure without mutated captured variables)

So if specification is not available in the near future, maybe we should update our definition of Fn traits so Fn does not overlapping with FnOnce + Clone?

I understand that someone may already implemented specific types that is Fn without Copy/Clone, but should this be deprecated? I think there is always better way to do the same thing.

earthengine commented Apr 16, 2018

As copy closures were already stablized in Beta, developers have more motivation to stabilizing on specialization now. The reason is that Fn and FnOnce + Clone represent two overlapping set of closures, and in many case we need to implement traits for both of them.

Just figure out that the wording of rfc 2132 seems to imply that there are only 5 types of closures:

  • FnOnce (a move closure with all captured variables being neither Copy nor Clone)
  • FnOnce + Clone (a move closure with all captured variables being Clone)
  • FnOnce + Copy + Clone (a move closure with all captured variables being Copy and so Clone)
  • FnMut + FnOnce (a non-move closure with mutated captured variables)
  • Fn + FnMut + FnOnce + Copy + Clone (a non-move closure without mutated captured variables)

So if specification is not available in the near future, maybe we should update our definition of Fn traits so Fn does not overlapping with FnOnce + Clone?

I understand that someone may already implemented specific types that is Fn without Copy/Clone, but should this be deprecated? I think there is always better way to do the same thing.

@glandium

This comment has been minimized.

Show comment
Hide comment
@glandium

glandium May 9, 2018

Contributor

Is the following supposed to be allowed by specialization (note the absence of default) or is it a bug?

#![feature(specialization)]
mod ab {
    pub trait A {
        fn foo_a(&self) { println!("a"); }
    }

    pub trait B {
        fn foo_b(&self) { println!("b"); }
    }

    impl<T: A> B for T {
        fn foo_b(&self) { println!("ab"); }
    }

    impl<T: B> A for T {
        fn foo_a(&self) { println!("ba"); }
    }
}

use ab::B;

struct Foo;

impl B for Foo {}

fn main() {
    Foo.foo_b();
}

without specialization, this fails to build with:

error[E0119]: conflicting implementations of trait `ab::B` for type `Foo`:
  --> src/main.rs:24:1
   |
11 |     impl<T: A> B for T {
   |     ------------------ first implementation here
...
24 | impl B for Foo {}
   | ^^^^^^^^^^^^^^ conflicting implementation for `Foo`
Contributor

glandium commented May 9, 2018

Is the following supposed to be allowed by specialization (note the absence of default) or is it a bug?

#![feature(specialization)]
mod ab {
    pub trait A {
        fn foo_a(&self) { println!("a"); }
    }

    pub trait B {
        fn foo_b(&self) { println!("b"); }
    }

    impl<T: A> B for T {
        fn foo_b(&self) { println!("ab"); }
    }

    impl<T: B> A for T {
        fn foo_a(&self) { println!("ba"); }
    }
}

use ab::B;

struct Foo;

impl B for Foo {}

fn main() {
    Foo.foo_b();
}

without specialization, this fails to build with:

error[E0119]: conflicting implementations of trait `ab::B` for type `Foo`:
  --> src/main.rs:24:1
   |
11 |     impl<T: A> B for T {
   |     ------------------ first implementation here
...
24 | impl B for Foo {}
   | ^^^^^^^^^^^^^^ conflicting implementation for `Foo`
@gnzlbg

This comment has been minimized.

Show comment
Hide comment
@gnzlbg

gnzlbg May 9, 2018

Contributor

@glandium what on earth is going on there? Nice example, here the playground link: https://play.rust-lang.org/?gist=fc7cf5145222c432e2bd8de1b0a425cd&version=nightly&mode=debug

Contributor

gnzlbg commented May 9, 2018

@glandium what on earth is going on there? Nice example, here the playground link: https://play.rust-lang.org/?gist=fc7cf5145222c432e2bd8de1b0a425cd&version=nightly&mode=debug

@nikomatsakis

This comment has been minimized.

Show comment
Hide comment
@nikomatsakis
Contributor

nikomatsakis commented May 10, 2018

@glandium that is #48444

@glandium

This comment has been minimized.

Show comment
Hide comment
@glandium

glandium May 10, 2018

Contributor

is it? there is no empty impl in my example.

Contributor

glandium commented May 10, 2018

is it? there is no empty impl in my example.

@MoSal

This comment has been minimized.

Show comment
Hide comment
@MoSal

MoSal May 10, 2018

@glandium

 impl B for Foo {}

MoSal commented May 10, 2018

@glandium

 impl B for Foo {}
@gnzlbg

This comment has been minimized.

Show comment
Hide comment
@gnzlbg

gnzlbg May 11, 2018

Contributor

@MoSal but that impl "isn't empty" since B adds a method with a default implementation.

Contributor

gnzlbg commented May 11, 2018

@MoSal but that impl "isn't empty" since B adds a method with a default implementation.

@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg May 11, 2018

Contributor

@gnzlbg It is empty by definition. Nothing between the braces.

Contributor

alexreg commented May 11, 2018

@gnzlbg It is empty by definition. Nothing between the braces.

@MoSal

This comment has been minimized.

Show comment
Hide comment
@MoSal

MoSal Jun 11, 2018

#![feature(specialization)]

use std::borrow::Borrow;

#[derive(Debug)]
struct Bla {
    bla: Vec<Option<i32>>
}

// Why is this a conflict ?
impl From<i32> for Bla {
    fn from(i: i32) -> Self {
        Bla { bla: vec![Some(i)] }
    }
}

impl<B: Borrow<[i32]>> From<B> for Bla {
    default fn from(b: B) -> Self {
        Bla { bla: b.borrow().iter().map(|&i| Some(i)).collect() }
    }
}

fn main() {
    let b : Bla = [1, 2, 3].into();
    println!("{:?}", b);
}
error[E0119]: conflicting implementations of trait `std::convert::From<i32>` for type `Bla`:
  --> src/main.rs:17:1
   |
11 | impl From<i32> for Bla {
   | ---------------------- first implementation here
...
17 | impl<B: Borrow<[i32]>> From<B> for Bla {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Bla`
   |
   = note: upstream crates may add new impl of trait `std::borrow::Borrow<[i32]>` for type `i32` in future versions

Wouldn't specialization prevent possible future conflicts?

MoSal commented Jun 11, 2018

#![feature(specialization)]

use std::borrow::Borrow;

#[derive(Debug)]
struct Bla {
    bla: Vec<Option<i32>>
}

// Why is this a conflict ?
impl From<i32> for Bla {
    fn from(i: i32) -> Self {
        Bla { bla: vec![Some(i)] }
    }
}

impl<B: Borrow<[i32]>> From<B> for Bla {
    default fn from(b: B) -> Self {
        Bla { bla: b.borrow().iter().map(|&i| Some(i)).collect() }
    }
}

fn main() {
    let b : Bla = [1, 2, 3].into();
    println!("{:?}", b);
}
error[E0119]: conflicting implementations of trait `std::convert::From<i32>` for type `Bla`:
  --> src/main.rs:17:1
   |
11 | impl From<i32> for Bla {
   | ---------------------- first implementation here
...
17 | impl<B: Borrow<[i32]>> From<B> for Bla {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Bla`
   |
   = note: upstream crates may add new impl of trait `std::borrow::Borrow<[i32]>` for type `i32` in future versions

Wouldn't specialization prevent possible future conflicts?

@alexreg

This comment has been minimized.

Show comment
Hide comment
@alexreg

alexreg Jun 12, 2018

Contributor

Goodness me, this is a slow-moving feature! No progress in over two years, it seems (certainly according to the original post). Has the lang team abandoned this?

Contributor

alexreg commented Jun 12, 2018

Goodness me, this is a slow-moving feature! No progress in over two years, it seems (certainly according to the original post). Has the lang team abandoned this?

@Centril

This comment has been minimized.

Show comment
Hide comment
@Centril
Contributor

Centril commented Jun 12, 2018

@mark-i-m

This comment has been minimized.

Show comment
Hide comment
@mark-i-m

mark-i-m Jun 12, 2018

Contributor

@alexreg It turns out soundness is hard. I believe there is some work on the "always applicable impls" idea currently happening, so there is progress. See #49624. Also, I believe that the chalk working group is working on implementing the "always applicable impls" idea too, but I don't know how far that has gotten.

Contributor

mark-i-m commented Jun 12, 2018

@alexreg It turns out soundness is hard. I believe there is some work on the "always applicable impls" idea currently happening, so there is progress. See #49624. Also, I believe that the chalk working group is working on implementing the "always applicable impls" idea too, but I don't know how far that has gotten.

@Lymia

This comment has been minimized.

Show comment
Hide comment
@Lymia

Lymia Jun 15, 2018

Contributor

After a bit of wrangling, it seems it is possible to effectively implement intersection impls already via a hack using specialization and overlapping_marker_traits.

https://play.rust-lang.org/?gist=cb7244f41c040db41fc447d491031263&version=nightly&mode=debug

Contributor

Lymia commented Jun 15, 2018

After a bit of wrangling, it seems it is possible to effectively implement intersection impls already via a hack using specialization and overlapping_marker_traits.

https://play.rust-lang.org/?gist=cb7244f41c040db41fc447d491031263&version=nightly&mode=debug

@Centril

This comment has been minimized.

Show comment
Hide comment
@Centril
Contributor

Centril commented Aug 27, 2018

@Boiethios

This comment has been minimized.

Show comment
Hide comment
@Boiethios

Boiethios Aug 28, 2018

I tried to write a recursive specialized function to implement an equivalent to this C++ code:

C++ code
#include <cassert>
#include <vector>

template<typename T>
size_t count(T elem)
{
    return 1;
}

template<typename T>
size_t count(std::vector<T> vec)
{
    size_t n = 0;
    for (auto elem : vec)
    {
        n += count(elem);
    }
    return n;
}

int main()
{
    auto v1 = std::vector{1, 2, 3};
    assert(count(v1) == 3);

    auto v2 = std::vector{ std::vector{1, 2, 3}, std::vector{4, 5, 6} };
    assert(count(v2) == 6);

    return 0;
}

I tried this:

Rust code
#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

default impl<T> Count for T {
    fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();
        
        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![
        vec![1, 2, 3],
        vec![4, 5, 6],
    ];
    assert_eq!(v.count(), 6);
}

But I am getting an:

overflow evaluating the requirement `{integer}: Count`

I do not think that this should happen because impl<T> Count for T where T::Item: Count should not overflow.

EDIT: sorry, I just saw that this was already mentioned

Boiethios commented Aug 28, 2018

I tried to write a recursive specialized function to implement an equivalent to this C++ code:

C++ code
#include <cassert>
#include <vector>

template<typename T>
size_t count(T elem)
{
    return 1;
}

template<typename T>
size_t count(std::vector<T> vec)
{
    size_t n = 0;
    for (auto elem : vec)
    {
        n += count(elem);
    }
    return n;
}

int main()
{
    auto v1 = std::vector{1, 2, 3};
    assert(count(v1) == 3);

    auto v2 = std::vector{ std::vector{1, 2, 3}, std::vector{4, 5, 6} };
    assert(count(v2) == 6);

    return 0;
}

I tried this:

Rust code
#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

default impl<T> Count for T {
    fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();
        
        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![
        vec![1, 2, 3],
        vec![4, 5, 6],
    ];
    assert_eq!(v.count(), 6);
}

But I am getting an:

overflow evaluating the requirement `{integer}: Count`

I do not think that this should happen because impl<T> Count for T where T::Item: Count should not overflow.

EDIT: sorry, I just saw that this was already mentioned

@TeXitoi

This comment has been minimized.

Show comment
Hide comment
@TeXitoi

TeXitoi Aug 28, 2018

Contributor

@Boiethios Your usecase is working if you default on the fn and not on the impl:

#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

impl<T> Count for T {
    default fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();

        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![vec![1, 2, 3], vec![4, 5, 6]];
    assert_eq!(v.count(), 6);
}
Contributor

TeXitoi commented Aug 28, 2018

@Boiethios Your usecase is working if you default on the fn and not on the impl:

#![feature(specialization)]

trait Count {
    fn count(self) -> usize;
}

impl<T> Count for T {
    default fn count(self) -> usize {
        1
    }
}

impl<T> Count for T
where
    T: IntoIterator,
    T::Item: Count,
{
    fn count(self) -> usize {
        let i = self.into_iter();

        i.map(|x| x.count()).sum()
    }
}

fn main() {
    let v = vec![1, 2, 3];
    assert_eq!(v.count(), 3);

    let v = vec![vec![1, 2, 3], vec![4, 5, 6]];
    assert_eq!(v.count(), 6);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment