Generic trait and specialization

Hello,

I try to implement for educational purposes a simple trait called Compare that implement equal function.
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=eb48536e70df9b31866be770eeb75528

In this example I don't understand why when I have an implementation in the generic trait, the compiler can't use the implementation for i32. When I don't put the implementation in the trait it work fine.

How can I tell the compiler to use the non specialized first then the generic if no specialization is found?

Thank you !

3 Likes

When you change it to explicit form, the compiler can explain why:

Compare::<i32>::equal(&1.0f32, &2i32)

the trait std::cmp::PartialEq<i32> is not implemented for f32

Your trait requires this to be true (and i32 is not comparable to f32 in Rust). In your implementation later you cast to avoid actually using that requirement, but the requirement exists anyway.

Yes but the problem here is that the compiler try to use the generic one instead of the specialized

Rust's unstable specialization feature is with default impls:

#![feature(specialization)]

pub trait Compare<T : ?Sized = Self> {
    fn equal(&self, other: &T) -> bool;
}

default impl<T: ?Sized> Compare<T> for T where T: std::cmp::PartialEq<T> {
    fn equal(&self, other: &T) -> bool {
        self == other
    }
}

As evidenced by that your code isn't refused by the compiler error "specialization is unstable", your code isn't doing specialization.

3 Likes

Thank you!
I just find this https://github.com/rust-lang/rfcs/pull/1210
Now this work like I'm expected ( The way I see it in C++ ) here:
https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=687729c397e97e51b580922abc253e4c

Thank you

There is no such thing here. There is one and only one definition of the trait that always applies everywhere to all its implementations. If the trait requires PartialEq, there is no way around it.

Rust checks validity against declarations, not against implementations.

Personally think it is more a compiler bug in allowing the definition;

    fn equal(&self, other: &i32) -> bool {

Trying polymorphic might give some breadcrumbs to follow;

    let f = 1.0f32;
    let c:&dyn Compare<i32> = &f;
    println!(" = {}", c.equal(&2i32));
warning: the trait `Compare` cannot be made into an object
 --> src/main.rs:6:8
  |
6 |     fn equal(&self, other: &T) -> bool 
  |        ^^^^^
  |
  = note: `#[warn(where_clauses_object_safety)]` on by default
  = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
  = note: for more information, see issue #51443 <https://github.com/rust-lang/rust/issues/51443>
  = note: method `equal` references the `Self` type in where clauses

error[E0277]: can't compare `dyn Compare<i32>` with `i32`
  --> src/main.rs:31:25
   |
31 |     println!(" = {}", c.equal(&2i32));
   |                         ^^^^^ no implementation for `dyn Compare<i32> == i32`
   |
   = help: the trait `std::cmp::PartialEq<i32>` is not implemented for `dyn Compare<i32>`

2 Likes

The problem here is that the compiler try to use the equal that required PartialEq but a specialization is made for types it complaining about. It should not take the generic trait impl but the specialized one. Well, this works with the nightly specialization :slight_smile:

Your example doesn't use the specialization feature, and the nightly specialization feature is known to have holes.

At least currently on stable, something implements a trait or a trait method if and only if it meets all the requirements in the trait definition. It doesn't matter what the implementation uses or doesn't use, as the trait definition is intentionally enforced even for purely theoretical reasons (so that e.g. your implementation can change in the future and start relying on stuff declared in the trait definition).

The compiler should probably complain about lack of repeated where for the method to avoid giving false impression that it's not there.

1 Like

Yes for me the compiler should complains about the

impl Compare<i32> for f32 {
    fn equal(&self, other: &i32) -> bool {
        print!("Compare f32 with i32");
        &(*self as i32) == other
    }
}

that is just not usable because the compiler will take the generic one

1 Like

In the first example yes. But in the second example ( with specialization feature ) the compiler choose the correct one. It's a lack of the compiler yes, I think.

What is odd, is that :

pub trait Compare<T : ?Sized = Self>
    where Self : core::cmp::PartialEq<T>
{
    // This not works
    fn equal(&self, other: &T) -> bool 
         {
            self == other
    }
}

Give the following error:

error[E0277]: can't compare `f32` with `i32`
  --> src/main.rs:21:6
   |
21 | impl Compare<i32> for f32 {
   |      ^^^^^^^^^^^^ no implementation for `f32 == i32`
   |
   = help: the trait `std::cmp::PartialEq<i32>` is not implemented for `f32`

For this, I'm ok with that, the where clause is apply to the trait and all the related functions.
In the initial case the where clause is not apply to the trait but to the function!
So logically the compiler should take the specialized one, if not, it's should complains about the fn equal in generic function and return that std::cmp::PartialEq<i32> is not implemented for f32 at the function level.

The report of the compiler is disturbing.

It is valid to create bounds that cannot be satisfied, e.g.:

trait Foo where Self: Drop + Copy {}

and it's valid to make an implementation that will never be applicable:

trait Foo {}
impl<T> Foo for T where Self: Drop + Copy {}

It's a type-system equivalent of if false {}.

The implementation with the cast compiled, because it didn't actually rely on the where clause thanks to the cast, so the code of the impl was valid and possible to compile.

In the second case the implementation was invalid itself and tried to perform unsupported operation right there, and there was no way to generate that code.

The difference is between "if pigs fly, make them wear goggles" (we know how to put goggles, so we'll do it when we see a flying pig) vs "make the pig fly" (error, unimplemented).

3 Likes

One question, does equals accept any type different than Self for T? It didn't seem so. Why should f32 of type Comparator<T> be compared with an type T=i32 if the constraint T=Self=f32 holds?