Skip to content
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
1 of 8 tasks
nikomatsakis opened this issue Feb 23, 2016 · 286 comments
Open
1 of 8 tasks

Tracking issue for specialization (RFC 1210) #31844

nikomatsakis opened this issue Feb 23, 2016 · 286 comments
Labels
A-specialization Area: Trait impl specialization A-traits Area: Trait system B-RFC-approved Approved by a merged RFC but not yet implemented. B-RFC-implemented Approved by a merged RFC and implemented. B-unstable Implemented in the nightly compiler and unstable. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. F-specialization `#![feature(specialization)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness S-tracking-design-concerns Blocking design concerns T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@nikomatsakis
Copy link
Contributor

nikomatsakis commented Feb 23, 2016

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

Major implementation steps:

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); see support default impl for specialization #37653 (comment) for some relevant examples of where default impl is limiting.
  • How should we deal with lifetime dispatchability?

Note that the specialization feature as implemented currently is unsound, which means that it can cause Undefined Behavior without unsafe code. min_specialization avoids most of the pitfalls.

@nikomatsakis nikomatsakis added A-traits Area: Trait system B-RFC-approved Approved by a merged RFC but not yet implemented. T-lang Relevant to the language team, which will review and decide on the PR/issue. B-unstable Implemented in the nightly compiler and unstable. labels Feb 23, 2016
@aturon
Copy link
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
Copy link
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
Copy link
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
Copy link
Contributor

sgrif commented Mar 20, 2016

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

@sgrif
Copy link
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
Copy link
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 pushed a commit to SergioBenitez/Rocket that referenced this issue Mar 23, 2016
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
Copy link
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
Copy link
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
Copy link
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
Copy link
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
Copy link
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
Copy link

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
Copy link
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
Copy link
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
Copy link
Contributor

Stebalien commented Apr 15, 2016

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

@arielb1
Copy link
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
Copy link

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
Copy link
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
Copy link

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
Copy link
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

@leontepe
Copy link

leontepe commented Jun 3, 2022

@bstrie

I suspect that no progress will be made until the types team is formed

It's there now. What are the next steps? I want to get involved!

@leontepe
Copy link

leontepe commented Jun 3, 2022

@c410-f3r

so it seems that everything points to a future without specialization.

Why settle? It's certainly possible.

@c410-f3r
Copy link
Contributor

c410-f3r commented Jun 3, 2022

@c410-f3r

so it seems that everything points to a future without specialization.

Why settle? It's certainly possible.

Like previously said in #31844 (comment), it is possible to reach out the responsible individuals (https://rust-lang.github.io/rfcs/3254-types-team.html#leads-and-membership) in the Rust Zulip platform at https://rust-lang.zulipchat.com/ or maybe through email for further instructions. I personally don't know their schedule, but chances are they want some people to get involve (https://rust-lang.zulipchat.com/#narrow/stream/122651-general/topic/.22good.20first.20issue.22/near/284068702).

Good luck!

@Logarithmus
Copy link
Contributor

Logarithmus commented Jun 18, 2022

Please support specialization on inherent impls for min_specialization. This seems like the most obvious use-case for specialization. For example, this pattern comes up all the time:

struct S<R: Read>;

impl<R: Read> S<R> {
    pub fn skip() {
        println!("read bytes to skip");
    }
}

impl<R: Read + Seek> S<R> {
    pub fn skip() {
        println!("use seek to skip (faster)");
    }
}

Not having this makes me very sad.

@kalcutter for such simple cases you can use https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html

@zirconium-n
Copy link

zirconium-n commented Jun 29, 2022

Please support specialization on inherent impls for min_specialization. This seems like the most obvious use-case for specialization. For example, this pattern comes up all the time:

struct S<R: Read>;

impl<R: Read> S<R> {
    pub fn skip() {
        println!("read bytes to skip");
    }
}

impl<R: Read + Seek> S<R> {
    pub fn skip() {
        println!("use seek to skip (faster)");
    }
}

Not having this makes me very sad.

@kalcutter for such simple cases you can use https://lukaskalbertodt.github.io/2019/12/05/generalized-autoref-based-specialization.html

Linked article literally said it can't solve the issue in a generic context at foreword.

@Centimo
Copy link

Centimo commented Jul 24, 2022

I found a strange compiler behavior, and I think it's a specialization bug.

#![feature(specialization)]

pub struct True{}
pub struct False{}

pub trait Boolean {}
impl Boolean for True{}
impl Boolean for False{}

pub trait Same_type< T > {}

impl< T > Same_type< T > for T {}

pub trait Is_pair {
  type Result: Boolean;
}

impl< First_g, Second_g > Is_pair for (First_g, Second_g) {
  type Result = True;
}

impl< T > Is_pair for T {
  default type Result = False;
}


fn implements_the_trait<U, T: Same_type< U >>() {}

fn main() {
  type Test_true = <(usize, usize) as Is_pair>::Result;
  println!("{}", std::any::type_name::<Test_true>());
  implements_the_trait::<Test_true, True>();
  
  type Test_false = <usize as Is_pair>::Result;
  println!("{}", std::any::type_name::<Test_false>());
  implements_the_trait::<Test_false, False>();
}
error[[E0277]](https://doc.rust-lang.org/nightly/error-index.html#E0277): the trait bound `False: Same_type<<usize as Is_pair>::Result>` is not satisfied
  --> src/main.rs:36:38
   |
36 |   implements_the_trait::<Test_false, False>();
   |                                      ^^^^^ the trait `Same_type<<usize as Is_pair>::Result>` is not implemented for `False`
   |
note: required by a bound in `implements_the_trait`

github-actions bot pushed a commit to rust-lang/glacier that referenced this issue Aug 5, 2022
=== stdout ===
=== stderr ===
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
 --> /home/runner/work/glacier/glacier/ices/43037.rs:2:12
  |
2 | #![feature(specialization)] // comment this line to get the expected behavior
  |            ^^^^^^^^^^^^^^
  |
  = note: `#[warn(incomplete_features)]` on by default
  = note: see issue #31844 <rust-lang/rust#31844> for more information
  = help: consider using `min_specialization` instead, which is more stable and complete

error[E0210]: type parameter `T` must be used as the type parameter for some local type (e.g., `MyStruct<T>`)
  --> /home/runner/work/glacier/glacier/ices/43037.rs:12:6
   |
12 | impl<T> From<<A<T> as Z>::Assoc> for T {}
   |      ^ type parameter `T` must be used as the type parameter for some local type
   |
   = note: implementing a foreign trait is only possible if at least one of the types for which it is implemented is local
   = note: only traits defined in the current crate can be implemented for a type parameter

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0210`.
==============
github-actions bot pushed a commit to rust-lang/glacier that referenced this issue Aug 5, 2022
=== stdout ===
=== stderr ===
warning: the feature `specialization` is incomplete and may not be safe to use and/or cause compiler crashes
 --> /home/runner/work/glacier/glacier/ices/45814.rs:1:12
  |
1 | #![feature(specialization)]
  |            ^^^^^^^^^^^^^^
  |
  = note: `#[warn(incomplete_features)]` on by default
  = note: see issue #31844 <rust-lang/rust#31844> for more information
  = help: consider using `min_specialization` instead, which is more stable and complete

error[E0275]: overflow evaluating the requirement `T: Trait<_>`
  |
  = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`45814`)
note: required because of the requirements on the impl of `Trait<_>` for `T`
 --> /home/runner/work/glacier/glacier/ices/45814.rs:5:20
  |
5 | default impl<T, U> Trait<T> for U { }
  |                    ^^^^^^^^     ^
  = note: 128 redundant requirements hidden
  = note: required because of the requirements on the impl of `Trait<_>` for `T`

error: aborting due to previous error; 1 warning emitted

For more information about this error, try `rustc --explain E0275`.
==============
gmoshkin pushed a commit to picodata/tarantool-module that referenced this issue Aug 8, 2022
`AsTuple` inherits from `serde::Serialize` which means it cannot be
implemented for Tuple.
Also the name is bad, because it makes you assume the conversion is
cheap when it's anything but.

`Encode` trait inherits from `serde::Serialize` and must be implemented
by user types instead of `AsTuple`.

`ToTupleBuffer` is used (almost) everywhere in the api and is
implemented for all `Encode` types. So it can be implemented for
`Tuple`.

We need 2 traits, because we can't have a default impl `ToTupleBuffer`
that relies on `serde::Serialize`, and we can't have a blanket impl for
`serde::Serialize`, because there are some types we want a different
impl for (e.g. `()`).

Hopefully `specialization` feature will be stabilized someday so that we
can get rid of `Encode`.

Hopefully...

specialization: rust-lang/rust#31844
@Mark-Simulacrum Mark-Simulacrum added the I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness label Aug 10, 2022
@rustbot rustbot added the I-prioritize Indicates that prioritization has been requested for this issue label Aug 10, 2022
@Mark-Simulacrum Mark-Simulacrum removed the I-prioritize Indicates that prioritization has been requested for this issue label Aug 10, 2022
@xfbs
Copy link

xfbs commented Aug 17, 2022

Any updates on this? @balanceglove2 have you been able to contact anyone?

@mankinskin
Copy link

mankinskin commented Aug 17, 2022

I think they are just waiting for pull requests at this point.

kkafar added a commit to ecrs-org/ecrs that referenced this issue Nov 25, 2022
<!-- If applicable - remeber to add the PR to the EA Rust project (ONLY
IF THERE IS NO LINKED ISSUE) -->

## Description

Add better defaults for builder.

Right now this PR is stuck, due to problems with implementation.

Rust lacks few important language mechanisms:

* Trait specialization (same as C++ template spec.) is in early
development process:
*
https://github.com/rust-lang/rfcs/blob/master/text/1210-impl-specialization.md
  * rust-lang/rust#31844
* Negative impls are unstable
  * rust-lang/rust#68318
*
https://doc.rust-lang.org/beta/unstable-book/language-features/negative-impls.html

It is an issue, because:

1. We want to provide a blanket implementation over `T: Chromosome` for
`Builder` struct. It would be ridiculous to force user to implement
custom `Builder`.
2. Different operators require different trait bounds on `Chromosome`,
thus defaults for different chromosome types must be different (there is
really no common ground).

Due to aforementioned language restrictions it seems impossible to
implement defaults depending on incoming chromosome type.

Edit:

There is hope however: Rust's macros - as they are quite powerfull - you
can operate directly on AST -- so it seems doable, but some research has
to be done first.

Edit 2: 

Defaults rarely make any sense as operators are problem specific. There
are really no operators that fit the needs of all problems (or are at
least valid).

It seems to me, that we must force end-user to specify operators
alongside fitness function.

Can we se macros somehow to manage this? 

#### Selected solution

So far we focus mostly on two types of chromosome: 

1. real valued (RVC)
2. bit string (BSC)

Therefore I went with following approach: for each chromosome type I
created a dedicated builder for which all parameters *except* operators
(outside the fixed type) can be modified.

There is also a single "dispatcher" - `ecrs::ga::Builder` which has
factory methods for other builders.

Following API is now available:

```rust
  ecrs::ga::Builder::with_rvc()
    .fitness_fn(rastrigin::rastrigin_fitness)
    .dim(5)
    .build()
    .run()
```

We should consider separating our API into two categories:

* static
* dynamic

Because most of API shape issues arise from the fact that we went for
fully static API. We could also expose `dynamic` API, which is
configurable in much easier way.

## Linked issues

Resolves #128 
Resolves #179 
Resolves #180
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-specialization Area: Trait impl specialization A-traits Area: Trait system B-RFC-approved Approved by a merged RFC but not yet implemented. B-RFC-implemented Approved by a merged RFC and implemented. B-unstable Implemented in the nightly compiler and unstable. C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. F-specialization `#![feature(specialization)]` I-unsound Issue: A soundness hole (worst kind of bug), see: https://en.wikipedia.org/wiki/Soundness S-tracking-design-concerns Blocking design concerns T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests