rust-lang/rust

Need negative trait bound

svmk opened this issue ยท 15 comments

svmk commented
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 {
  ...
}

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.

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.

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() {}
kdy1 commented

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

+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(_) => ...

@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>);
}

@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

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 2N impls)

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

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

Ixrec commented

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

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!

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.

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.

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.