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

Need negative trait bound #42721

Open
svmk opened this issue Jun 17, 2017 · 15 comments
Open

Need negative trait bound #42721

svmk opened this issue Jun 17, 2017 · 15 comments
Labels
A-specialization Area: Trait impl specialization A-trait-system Area: Trait system C-feature-request Category: A feature request, i.e: not implemented / a PR. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@svmk
Copy link

svmk commented Jun 17, 2017

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 {
  ...
}
@oli-obk
Copy link
Contributor

oli-obk commented Jun 17, 2017

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.

@oli-obk
Copy link
Contributor

oli-obk commented Jun 17, 2017

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.

@Mark-Simulacrum Mark-Simulacrum added the A-specialization Area: Trait impl specialization label Jun 23, 2017
@Mark-Simulacrum Mark-Simulacrum added the C-feature-request Category: A feature request, i.e: not implemented / a PR. label Jul 27, 2017
@soltanmm
Copy link

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

kdy1 commented Nov 27, 2017

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

@axos88
Copy link

axos88 commented Jul 22, 2018

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

@kennytm
Copy link
Member

kennytm commented Jul 22, 2018

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

@stepancheg
Copy link
Contributor

stepancheg commented Sep 14, 2019

@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

@kennytm
Copy link
Member

kennytm commented Sep 14, 2019

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)

@axos88
Copy link

axos88 commented Sep 14, 2019

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

@quark-zju
Copy link
Contributor

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

@Ixrec
Copy link
Contributor

Ixrec commented Apr 17, 2020

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

@tib888
Copy link

tib888 commented May 9, 2020

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!

@steveklabnik steveklabnik added the T-lang Relevant to the language team, which will review and decide on the PR/issue. label May 11, 2020
@newpavlov
Copy link
Contributor

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.

@ghost
Copy link

ghost commented Aug 26, 2020

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.

@camelid camelid added the A-trait-system Area: Trait system label Oct 9, 2020
@tvallotton
Copy link
Contributor

tvallotton commented Jun 23, 2021

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.

@fmease fmease added the A-trait-system Area: Trait system label Dec 21, 2024
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-trait-system Area: Trait system C-feature-request Category: A feature request, i.e: not implemented / a PR. 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