Need negative trait bound

feddf16
Opened by svmk at 2025-03-04 23:52:33
use std::convert::From;

struct SomeError;
enum Error<E> {
    One(SomeError),
    Two(E),
}

/// impl<E> From<SomeError> for Error<E> where E: !SomeError {
impl<E> From<E> for Error<E> {
    fn from(error: E) -> Self {
        Error::One(error)
    }
}
impl<E> From<SomeError> for Error<E> {
    fn from(error: E) -> Self {
        Error::Two(error)
    }
}

It's produces error:

rustc 1.18.0 (03fc9d622 2017-06-06)
error: main function not found

error[E0119]: conflicting implementations of trait `std::convert::From<SomeError>` for type `Error<SomeError>`:
  --> <anon>:15:1
   |
9  | / impl<E> From<E> for Error<E> {
10 | |     fn from(error: E) -> Self {
11 | |         Error::One(error)
12 | |     }
13 | | }
   | |_- first implementation hereadd
14 | 
15 | / impl<E> From<SomeError> for Error<E> {
16 | |     fn from(error: E) -> Self {
17 | |         Error::Two(error)
18 | |     }
19 | | }
   | |_^ conflicting implementation for `Error<SomeError>`

error: aborting due to previous error

May'be shall implement contruction:

impl<E> From<SomeError> for Error<E> where E: !SomeError {
  ...
}
  1. Negative bounds have a lot of issues. Like the fact that implementing a trait becomes a breaking change. There's some related discussion in https://github.com/rust-lang/rfcs/issues/1834 . There's probably more discussion in the internals forum.

    Oli Scherer at 2017-06-17 19:42:50

  2. Oh and you might want to have a look at specialization. I think it allows your case without the Pandora's box of issues that negative trait bounds brings with it.

    Oli Scherer at 2017-06-17 19:44:15

  3. AFAICT From's blanket implementation without use of the default keyword precludes specialization at the current time, unfortunately. An alternative formulation is the OIBIT trick for negative reasoning (someone mentioned this somewhere a while back; I don't remember who or where):

    #![feature(optin_builtin_traits)]
    
    trait NotEq {}
    impl NotEq for .. {}
    impl<X> !NotEq for (X, X) {}
    
    struct X<A>(A);
    impl<A, B> From<X<B>> for X<A>
        where
            A: From<B>,
            (A, B): NotEq,
    {
        fn from(a: X<B>) -> Self { X(a.0.into()) }
    }
    
    fn main() {}
    

    soltanmm at 2017-08-14 04:06:14

  4. Does that works for a struct with (u8, u8)? Considering how oibit (Send/Sync) works, I don't think it would..

    Donny/강동윤 at 2017-11-27 09:32:55

  5. +1

    I'm having trouble with implementing Debug for structs that have fields which are not Debug:

    enum MyOption<T> {
      None,
      Some(T)
    }
    
    impl<T> Debug for MyOption<T> where T: ?Debug {
              match self {
                None => write!(f, "Unset"),
                Some(_) => write!(f, "An unprintable value")         
            }
    }
     
    #[derive(Debug)]
    struct Foo {
      a: u32,
      b: MyOption<SomethingWithoutDebug>
    }
    

    Another solution for my case would be if such a pattern could be implemented:

      self match {
        None => ...
        Some(x: T where T: Debug) => ...
        Some(_) => ...
    

    Akos Vandra-Meyer at 2018-07-22 10:18:21

  6. @axos88 This is already supported through specialization.

    #![feature(specialization)]
    
    use std::fmt::{self, Debug};
    
    enum MyOption<T> {
      None,
      Some(T)
    }
    
    impl<T> Debug for MyOption<T> {
        default fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match self {
                MyOption::None => write!(f, "Unset"),
                MyOption::Some(_) => write!(f, "An unprintable value"),
            }
        }
    }
    
    impl<T: Debug> Debug for MyOption<T> {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            match self {
                MyOption::None => write!(f, "Unset"),
                MyOption::Some(t) => t.fmt(f),
            }
        }
    }
    
    struct Unprintable;
    
    fn main() {
        println!("{:?}", MyOption::Some(1234));
        println!("{:?}", MyOption::Some(Unprintable));
        println!("{:?}", MyOption::None::<Unprintable>);
    }
    

    kennytm at 2018-07-22 11:58:34

  7. @kennethbgoodin more complicated use cases are not possible with trait specialization.

    For example:

    #![feature(specialization)]
    
    trait Integer {
        fn to_int(&self) -> i32;
    }
    
    trait Collection {
        fn to_vec(&self) -> Vec<i32>;
    }
    
    trait PrintAnything {
        fn print(&self) -> String;
    }
    
    impl<T> PrintAnything for T {
        default fn print(&self) -> String {
            format!("unprintable")
        }
    }
    
    impl<T: Integer> PrintAnything for T {
        fn print(&self) -> String {
            format!("int {}", self.to_int())
        }
    }
    
    impl<T: Collection> PrintAnything for T {
        fn print(&self) -> String {
            format!("collection {:?}", self.to_vec())
        }
    }
    
    error[E0119]: conflicting implementations of trait `PrintAnything`:
      --> src/main.rs:27:1
       |
    21 | impl<T: Integer> PrintAnything for T {
       | ------------------------------------ first implementation here
    ...
    27 | impl<T: Collection> PrintAnything for T {
       | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation
    
    error: aborting due to previous error
    

    Stepan Koltsov at 2019-09-14 00:19:53

  8. It is possible if specialization is enhanced to recognize intersection (previously attempted in #49624) i.e.

    impl<T> PrintAnything for T { ... }
    impl<T: Integer> PrintAnything for T { ... }
    impl<T: Collection> PrintAnything for T { ... }
    impl<T: Integer + Collection> PrintAnything for T { ... }
    

    (If you need to specialize to N traits like these you'll need 2<sup>N</sup> impls)

    kennytm at 2019-09-14 06:05:56

  9. @kennytm That doesn't seem like a solution for anything with N >= 4...

    Akos Vandra-Meyer at 2019-09-14 08:15:08

  10. Would a subset of negative bounds be less problematic? For example, !Sized? That alone might already solve some problems.

    Jun Wu at 2020-04-12 20:14:12

  11. https://github.com/rust-lang/rust/pull/68004 should probably be mentioned here. It's obviously not a complete negative bounds feature, and the motivation is largely unrelated, but it's a step in this direction.

    Ixrec at 2020-04-17 20:40:11

  12. error[E0119]: conflicting implementations of trait PrintAnything: --> src/main.rs:27:1 | 21 | impl<T: Integer> PrintAnything for T { | ------------------------------------ first implementation here ... 27 | impl<T: Collection> PrintAnything for T { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation

    error: aborting due to previous error

    I have run into pretty much the same situation. "conflicting implementation" check should consider the type constraints!

    tib at 2020-05-09 11:20:15

  13. Another example which requires negative trait bounds:

    trait Foo { .. }
    trait Bar { .. }
    impl<T: Foo> Bar for T { .. }
    impl<T: Foo> Foo for &T { .. }
    impl<T: Bar> Bar for &T { .. }
    

    I am not sure if specialization can help here.

    Artyom Pavlov at 2020-07-07 07:38:03

  14. In my opinion, negative trait bounds is something we do need as a feature in the language along with specialization.

    1. When building a package on the bottom end of the dependency chain, such as an executable, we are, most of the time, protected by the "orphan rule".
    2. When building a package in general, people do tend to use the specific version they need, so breaking changes are still possible, but avoidable.
    3. Maybe this feature can be split into two parts, just like specialization is currently. First one giving the ability to only apply negative bounds when both the negative bound trait and the type that is implementing the trait are defined in the same crate as the implementation. While the other containing the rest of the features that come with it (stricter version of the "orphan rule"). Where the second can be, for example, "perma-unstable" like some other features.
    4. We already have a book with a chapter about making the code future-proof, so why not just put one paragraph for this one too, since existing code can make breaking changes even without negative trait bounds.
    5. Specialization is not a panacea.

    Deleted user at 2020-08-26 21:46:14

  15. Negative bounds have a lot of issues. Like the fact that implementing a trait becomes a breaking change. There's some related discussion in rust-lang/rfcs#1834 . There's probably more discussion in the internals forum.

    This could be address by mandating complementary implementations. So if you want to write something like

    impl<T: !Foo> Bar for T { \* ... *\ }
    

    you'd be forced to implement

    impl<T: Foo> Bar for T { \* ... *\ }
    

    Therefore implementing Foo cannot break code because T has to implement Bar regardless of whether it implements T. I explained this more thoroughly here.

    tvallotton at 2021-06-23 05:03:01

  16. I have a use-case where this would be really helpful: I'm making some changes to trie-rs, a trie structure crate with two APIs: one that is map-like (Trie<Token, Value>) and another that is set-like (Trie<Token>). The set trie is simply a newtype of the map trie (Trie<Token, ()>) that removes methods for accessing values (like HashSet is a newtype of HashMap in the standard library).

    It would be nice to remove the newtype and simply make the set trie a type alias of the map trie by using negative bounds to disable methods for sets.

    e.g.

    trait Unit {}
    
    impl Unit for () {}
    
    impl<Token, Value> Trie<Token, Value> {
      fn get_value(&self, label: impl Label<Token>) -> Option<&Value>
      where Value: !Unit
      {
        // ...
      }
    }
    

    I also have NodeRef and KeyRef types that could be merged with the same pattern.

    Alternatives:

    • General auto traits (#13231) (i.e. auto trait NotUnit {} and impl !NotUnit for () {} so where Value: NotUnit)
    • Auto traits for ZST and non-ZST types (i.e. where Value: NotZst)
    • Specialization with associated marker types (i.e. where Value: Size<Kind = NonZero> 😬)
    • Concrete negative bounds (IDK what it's called) (i.e. where Value != ())

    But negative trait bounds was what first came naturally to me, plus the idea of a trait that is minimally implemented is aesthetically appealing to me (unlike auto traits which get implemented for everything).

    Jonah Henriksson at 2025-03-02 21:27:16

  17. @JonahPlusPlus This looks like bad design to me.

    Why should it be forbidden to call get_value ONLY if the if the value is ()? There isn't a good reason for it.

    Maybe you could make it a crate internal type or something, which you don't expose to public, so nobody would be able to create a Trie<Token, CrateInternalType> directly, and maybe you could ensure that no user would get a reference to such a type. This way you could just not call get_value for this type. But not sure if something like this would work here.

    Fabio Krapohl at 2025-03-04 21:49:47

  18. @porky11 Yeah, it could use an internal type, but the point is that it would still need negative bounds or some alternative in order to merge the set and map variants.

    Jonah Henriksson at 2025-03-04 23:52:33