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

Can't write non-overlapping blanket impls that involve associated type bindings #20400

Open
japaric opened this issue Jan 1, 2015 · 20 comments
Labels
A-associated-items Area: Associated items such as associated types and consts. A-traits Area: Trait system C-bug Category: This is a bug. needs-rfc This change is large or controversial enough that it should have an RFC accepted before doing it. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@japaric
Copy link
Member

japaric commented Jan 1, 2015

STR

Example from libcore:

#![crate_type = "lib"]
#![feature(associated_types)]
#![no_implicit_prelude]

trait Iterator {
    type Item;
}

trait AdditiveIterator {
    type Sum;

    fn sum(self) -> Self::Sum;
}

impl<I> AdditiveIterator for I where I: Iterator<Item=u8> {  //~error conflicting implementation
    type Sum = u8;

    fn sum(self) -> u8 { loop {} }
}

impl<I> AdditiveIterator for I where I: Iterator<Item=u16> {  //~note conflicting implementation here
    type Sum = u16;

    fn sum(self) -> u16 { loop {} }
}

Version

7d4f487

No type can implement both Iterator<Item=u8> and Iterator<Item=u16>, therefore these blanket impls are non overlapping and should be accepted.

cc @nikomatsakis

@kmcallister kmcallister added A-traits Area: Trait system A-associated-items Area: Associated items such as associated types and consts. labels Jan 16, 2015
@lambda-fairy
Copy link
Contributor

I encountered this bug too, while trying to optimize ToString (#18404):

pub enum Void {}

pub trait Display {
    // ...
    type AsStr = Void;
    fn fmt_as_str(&self) -> &<Self as Display>::AsStr { unimplemented!() }
}

pub trait ToString {
    fn to_string(&self) -> String;
}

impl<T: Display<AsStr=Void> + ?Sized> ToString for T {
    fn to_string(&self) -> String {
        format!("{}", self)
    }
}

impl<T: Display<AsStr=str> + ?Sized> ToString for T {
    fn to_string(&self) -> String {
        String::from_str(self.fmt_as_str())
    }
}

This change could speed up "".to_string() significantly (currently, String::from_str is between 2 and 5 times faster than format!) so I'd love to see this issue fixed.

@edwardw

This comment has been minimized.

1 similar comment
@jroesch

This comment has been minimized.

@withoutboats
Copy link
Contributor

This is really causing problems for a crate I'm working on.

@cristicbz
Copy link
Contributor

Is this bug likely to get any love? I keep hitting this (see #23341) too. I had a look around by I can't really follow how overlapping impls are determined, I would have liked to have a go at it.

@sgrif
Copy link
Contributor

sgrif commented Jan 18, 2016

Any chance of this getting triaged any time soon? Also feeling pains from this issue.

sgrif added a commit to diesel-rs/diesel that referenced this issue Jan 18, 2016
This is part of a greater effort to clean up some of the messier bits of
our type system. Unfortunately, we still can't implement something both
for null and not null in a non-overlapping way most of the time, as we
still will bump into rust-lang/rust#20400.

Otherwise we could write something like:

```rust
impl<T, ST> Expression for Something<T> where
    T: Expression<SqlType=ST>,
    ST: NotNull,
{
}

impl<T, ST> Expression for Something<T> where
    T: Expression<SqlType=Nullable<ST>>,
    ST: NotNull,
{
}
```

While these clearly do not overlap, rust treats them as overlapping.
Unfortunately this is just one more type that we need to implement for
new SQL types added in the future. I can't just make `IntoNullable`
be a requirement of `NativeSqlType`, as it would require specifying the
nullable case when packing it into a trait object.

`NotNull` can just be replaced with an OIBIT in the future.
@Kixunil
Copy link
Contributor

Kixunil commented May 1, 2017

I ran into this problem just now. I wanted to provide and_then() and map() methods for my type that would work if Trait::Type is Option or Result.

@ZerothLaw
Copy link

What's needed to advance this issue towards a fix? Are we dependent on Chalk?

@ZerothLaw
Copy link

Looks like you can kind of "trick" the compiler by using type specialization. On stable, 2015 edition:

https://play.rust-lang.org/?gist=d803059c64390d7182445bb21756f98c&version=stable&mode=debug&edition=2015

@skade
Copy link
Contributor

skade commented Dec 25, 2018

I'd like to add myself to the list of people that this causes problems for :).

@dbeckwith
Copy link

Looks like you can kind of "trick" the compiler by using type specialization. On stable, 2015 edition:

https://play.rust-lang.org/?gist=d803059c64390d7182445bb21756f98c&version=stable&mode=debug&edition=2015

@ZerothLaw could you explain your trick a bit? I think I'm running into this issue and am hoping for a workaround but I'm struggling to understand your example. If you could give some advice on how to apply that to the code in #58171 that would be great.

@jonas-schievink jonas-schievink added needs-rfc This change is large or controversial enough that it should have an RFC accepted before doing it. T-lang Relevant to the language team, which will review and decide on the PR/issue. labels May 16, 2019
@steveklabnik
Copy link
Member

Triage: these kinds of impls are still not accepted yet.

phoracek added a commit to zlosynth/graphity that referenced this issue Jan 3, 2021
The existing implementation of the graphity! macro does not allow for
import of an external node. That was due to a limitation caused by
rust-lang/rust#20400.

With this patch, the signature of the macro changes. It now accepts
direct reference to the node and its producer and consumer.

With this change, it is possible to use nodes defined in other crates.

Signed-off-by: Petr Horáček <phoracek@redhat.com>
@SoniEx2
Copy link
Contributor

SoniEx2 commented Mar 8, 2021

We wanted FnMut(&char) -> bool to be Pattern so you could use char::is_ascii_digit but this prevents that.

the8472 added a commit to the8472/rust that referenced this issue Jul 16, 2021
Due to rust-lang#20400 the corresponding TrustedLen impls need a helper trait
instead of directly adding `Item = &[T;N]` bounds.
Since TrustedLen is a public trait this in turn means
the helper trait needs to be public. Since it's just a workaround
for a compiler deficit it's marked hidden, unstable and unsafe.
dfinity-bot pushed a commit to dfinity/ic that referenced this issue Apr 6, 2022
Payload Builder Refactor (Part 1)

This MR refactors the way, the `BatchPayload` is being constructed.


The idea is quite simple: I am introducing a new trait called `BatchPayloadSectionBuilder`.
It is supposed to replace the traits `XNetPayloadBuilder`, `SelfValidatingPayloadBuilder` and `IngressSelector` (and also the not yet implemented `CanisterHttpPayloadBuilder`), as they all do the same things.

Then inside consensus, there is wrapper called `BatchPayloadSectionAdapter`, that is responsible for mapping the sections into the `BatchPayload`.
This boilerplate is unfortunately necessary, to eliminate the Generic in `BatchPayloadSectionBuilder`.
My original design would use another trait, however, this approach was prevented because of a [long standing compiler bug](rust-lang/rust#20400).


On the validation side, every validation call also returns its `byte_size`, so we can check, that the overall size is not exceeded either.

This MR also contains the implementations of `BatchPayloadSectionBuilder` on top the existing traits. This way, We don't need to touch test code for now. We might want to remove the old traits at a later date.

The payload builder itself just has a Vec<BatchPayloadSectionAdapter> that it calls in a rotating order on a default `BatchPayload`.

**NOTE**: This MR does NOT contain the functional changes to `payload_builder.rs`. Those will be made in a separate MR, to keep the scope of this MR small. 

See merge request dfinity-lab/public/ic!3543
copybara-service bot pushed a commit to google/crubit that referenced this issue Aug 1, 2022
(Technically, this does let you call trait methods with nontrivial arguments,
but it's not in a final/decent place.)

This is an alternate version of unknown commit which tried to continue to defer
materialization. However, as discussed in[]
currently impossible.

In particular, we cannot define a trait like this:

```rs
impl<T: Ctor<Output=A>> MyTrait<T> for S {...}
impl<T: Ctor<Output=B>> MyTrait<T> for S {...}
```

... because Rust does not understand that these impls are disjoint:

rust-lang/rust#20400

#### What's next?

(Apologies if this is a bit unorganized, I've spent too much time in the trenches.)

So this CL is just a first step: we *must*  monomorphize the implementation.
Rather than accepting any `T: Ctor`, accept an `RvalueReference` (a concrete type).

After this CL, I think we have a slightly more open field than I thought. In particular,
we should be able to regain the `Ctor` API, except using only *one* parameterized impl.
So, instead of the broken impls above, we can have this impl:

```rs
impl<'a> MyTrait<RvalueReference<'a, A>> for S {...}
impl<'a> MyTrait<RvalueReference<'a, B>> for S {...}

impl<U, CtorType> MyTrait<CtorType> for S
where
  &C : for<'a> MyTrait<RvalueReference<'a, U>>,
  CtorType: Ctor<Output=U>
{...}
```

Because this is only _one_ parameterized impl, there's no conflicts. It is
implemented in terms of the concrete non-parameterized impls as generated by
this change.

However, I'm not yet 100% certain this will work, and it is actually not a small task
to do, even on top of this CL. For example, there's a bunch of refactoring to let one
generate a second blanket impl using knowledge about the trait function etc. from the
concrete impl.

##### RvalueReference might need to get replaced.

If we can use the `Ctor` approach described above... we can't use `RvalueReference`,
actually, because Rust will then recurse infinitely. The `RvalueReference` type used
for the `for<'a> MyTrait<RvalueReference<...>>` bound must be in the _same_ crate so
that Rust knows that `RvalueReference` doesn't itself impl `Ctor`. And unfortunately,
no, negative impls aren't good enough here, yet, apparently. At least, it didn't
resolve it when I tried it!

You can test this in a local two-crate setup.

Crate 1:

```rs
pub trait Ctor {
  type Output;
}
pub struct RvalueReference<'a, T>(&'a T);
```

Crate 2:

```rs
use lib1::*;

pub struct A1;
pub struct A2;
pub struct B;

impl <'a> From<RvalueReference<'a, A1>> for B {
  fn from(_: RvalueReference<'a, A1>) -> Self { todo!(); }
}

impl <'a> From<RvalueReference<'a, A2>> for B {
  fn from(_: RvalueReference<'a, A2>) -> Self { todo!(); }
}

impl <T: Ctor> From<T> for B
where B : for<'a> From<RvalueReference<'a, T::Output>> {
  fn from(_: T) -> Self { todo!(); }
}
```

If you build crate 2, it will fail with the following error:

```
error[E0275]: overflow evaluating the requirement `for<'a> B: From<lib1::RvalueReference<'a, _>>`
   |
   = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`lib2`)
note: required because of the requirements on the impl of `for<'a> From<lib1::RvalueReference<'a, _>>` for `B`
  --> src/lib.rs:15:16
   |
15 | impl <T: Ctor> From<T> for B
   |                ^^^^^^^     ^
   = note: 126 redundant requirements hidden
   = note: required because of the requirements on the impl of `for<'a> From<lib1::RvalueReference<'a, _>>` for `B`

For more information about this error, try `rustc --explain E0275`.
error: could not compile `lib2` due to previous error
```

But it will work fine if you move `RvalueReference` to another crate!

##### If all else fails, we'll force the caller to materialize the Ctor

If even the approach outlined above doesn't work, well, we'll just have to force callers
to materialize the `Ctor`: call `Trait::method(mov(emplace!(foo())))` instead of
`Trait::method(foo())`.

That's what I described in[]
but I'm hoping we can work our way out after all!

Either way, both approaches build on this change. Investigating the followups may take some
time, so I'd rather not leave this change sitting around generating merge conflicts,
if possible. :X

PiperOrigin-RevId: 464613254
@hlbarber
Copy link

hlbarber commented Mar 10, 2023

There is also an associated const version of this where

#![feature(associated_const_equality)]

trait Foo { const ENABLED: bool; }

trait Bar {}

impl<T> Bar for T where T: Foo<ENABLED = true> {}
impl<T> Bar for T where T: Foo<ENABLED = false> {}

shouldn't conflict.

@GoldsteinE
Copy link
Contributor

Interestingly, it still fails with -Z trait-solver=next

@Ofenhed
Copy link

Ofenhed commented Apr 12, 2023

This is possible on stable already, but not in a user friendly way.

@mversic
Copy link

mversic commented Sep 23, 2023

This crate enables you to write disjoint implementations in a straightforward way

@rakshith-ravi
Copy link
Contributor

@mversic This looks great! Would it be possible for the same thing to work with functions as well?

Example:

pub trait RouterExt {
  fn mount_route<E: ApiEndpoint<Auth = NoAuth>>(self, endpoint: E) -> Self;
  fn mount_route<E: ApiEndpoint<Auth = SomeAuth>>(self, endpoint: E) -> Self;
}

This would allow different functions to be called depending on the associated type of the endpoint. Is this possible? I went through the tests, but couldn't find a similar example

@mversic
Copy link

mversic commented Sep 24, 2023

@mversic This looks great! Would it be possible for the same thing to work with functions as well?

no, you can't have two methods of the same name under one trait. I believe you should be able to do sth like this:

use disjoint_impls::disjoint_impls;

trait ApiEndpointDispatch {
    type Auth;
}

enum NoAuth {}
enum SomeAuth {}

struct Endpoint1 {}
impl ApiEndpointDispatch for Endpoint1 {
    type Auth = NoAuth;
}

struct Endpoint2 {}
impl ApiEndpointDispatch for Endpoint2 {
    type Auth = SomeAuth;
}

disjoint_impls! {
    pub trait ApiEndpoint {}
    impl<E: ApiEndpointDispatch<Auth = NoAuth>> ApiEndpoint for E {}
    impl<E: ApiEndpointDispatch<Auth = SomeAuth>> ApiEndpoint for E {}
}

pub trait RouterExt {
  fn mount_route<E: ApiEndpoint>(self, endpoint: E) -> Self;
}

zjp-CN added a commit to zjp-CN/term-rustdoc that referenced this issue Feb 3, 2024
but due to limitation of rustc
rust-lang/rust#20400
we can't have two new functions based on non-overlapping associated types
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-associated-items Area: Associated items such as associated types and consts. A-traits Area: Trait system C-bug Category: This is a bug. needs-rfc This change is large or controversial enough that it should have an RFC accepted before doing it. 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