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

d97a6cd
Opened by Jorge Aparicio at 2024-11-29 18:24:19

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

  1. 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.

    Chris Wong at 2015-01-29 23:24:24

  2. cc me

    Edward Wang at 2015-02-15 00:39:36

  3. cc me

    Jared Roesch at 2015-02-18 10:11:31

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

    srrrse at 2015-05-07 02:10:40

  5. 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.

    Cristi Cobzarenco at 2015-10-16 17:16:22

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

    Sage Griffin at 2016-01-18 21:00:45

  7. 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.

    Martin Habovštiak at 2017-05-01 21:23:27

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

    Tyler J. Laing at 2018-08-13 20:42:25

  9. 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

    Tyler J. Laing at 2018-08-13 21:39:24

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

    Florian Gilcher at 2018-12-25 22:05:33

  11. 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.

    Daniel Beckwith at 2019-02-04 23:13:55

  12. To give an update on this: In 2016, RFC 1672 was proposed to fix this, but was postponed until the Chalk integration is done. Additionally, according to https://github.com/rust-lang/rfcs/pull/1672#issuecomment-262152934, allowing these kinds of impls would allow users to express mutually exclusive traits, which is a very powerful feature that needs more in-depth consideration by the language team (hence marking as blocked on an RFC as well).

    (also adding the keyword "disjoint" to make this issue easier to find)

    Jonas Schievink at 2019-05-16 19:46:22

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

    Steve Klabnik at 2020-12-31 22:01:49

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

    Soni L. at 2021-03-08 02:20:48

  15. 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.

    Harry Barber at 2023-03-10 12:35:05

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

    Max “Goldstein” Siling at 2023-04-12 19:48:11

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

    Marcus Ofenhed at 2023-04-12 20:05:43

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

    Marin Veršić at 2023-09-23 12:42:57

  19. @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

    Rakshith Ravi at 2023-09-23 17:36:01

  20. @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;
    }
    

    Marin Veršić at 2023-09-24 03:17:38

  21. This seems to have been raised at the start of 2015 and here in October 2024 I get:

    $cargo -V -v cargo 1.82.0 (8f40fc59f 2024-08-21) release: 1.82.0 commit-hash: 8f40fc59fb0c8df91c97405785197f3c630304ea commit-date: 2024-08-21 host: aarch64-apple-darwin libgit2: 1.8.1 (sys:0.19.0 vendored) libcurl: 8.7.1 (sys:0.4.74+curl-8.9.0 system ssl:(SecureTransport) LibreSSL/3.3.6) ssl: OpenSSL 1.1.1w 11 Sep 2023 os: Mac OS 15.0.1 [64-bit]

    $cargo build Compiling nonover v0.1.0 (/Users/rod/code/rust/triage/nonover) error[E0119]: conflicting implementations of trait AdditiveIterator --> src/lib.rs:21:1 | 15 | impl<I> AdditiveIterator for I where I: Iterator<Item=u8> { //~error conflicting implementation | --------------------------------------------------------- first implementation here ... 21 | impl<I> AdditiveIterator for I where I: Iterator<Item=u16> { //~note conflicting implementation here | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation For more information about this error, try rustc --explain E0119. error: could not compile nonover (lib) due to 1 previous error

    So the "bug" or "lack of feature" remains after nearly 10 years. Is it time to decide to "fix" or say "not part of the language"?

    Rod at 2024-10-19 18:49:06

  22. For those expecting it to work with the new trait solver automatically, here's a reminder that it's blocked on an RFC as it unlocks mutually exclusive traits.

    QuineDot at 2024-11-29 18:24:19