Specialization precedence rules

I'm apparently not quite following the precedence rules as described in the RFC.

It seems to me that Bar should be considered more specific than T, but the compiler disagrees.

#![feature(specialization)]

struct Bar {}

enum Foo<T> {
    Bar(Bar),
    Thing(T),
}

trait FooIt<T> {
    fn foo(self) -> Foo<T>;
}

impl<T> FooIt<T> for T {
    default fn foo(self) -> Foo<T> {
        Foo::Thing(self)
    }
}

impl<T> FooIt<T> for Bar {
    fn foo(self) -> Foo<T> {
        Foo::Bar(self)
    }
}

(Playground)

I tried adding extra type params to Bar and even Foo to play with the specificity, but at best calling my_bar.foo() just uses the T impl.

I tried doubling down with:

unsafe auto trait Thingable {}
impl !Thingable for Bar {}

impl<T: Thingable> FooIt<T> for T {

But then trying to call bar.foo() fails with

the trait bound Bar: Thingable is not satisfied in Bar

Is there some other way to either make the impl .. for Bar more specific or exclude Bar from the impl .. for T?

I don't know the actual rules, but it seems to me that Foo<T> for T is a generic one-to-one implementation, but Foo<T> for Bar is many-to-one. Maybe that's involved in deciding what's more specific.

Does it compile? My guess is that neither of them specializes the other and the situation is a plain overlap. Playground says it doesn't compile.

It does not compile, and I agree that apparently neither is more specialized. I’m trying to understand why my intuition that the Bar impl should be more specialized is wrong and if there’s any way to make something like this work.

Thanks!

AIUI, to be more specialized, one must be a strict subset of the other. Both of yours apply to FooIt<Bar> for Bar, which is the overlap, but they both have distinct coverage too. For example, FooIt<T> for T creates FooIt<i32> for i32, and FooIt<T> for Bar creates FooIt<i32> for Bar.

So their main divergence comes having T in two places, so the FooIt type parameter may or may not be required to be the same as Self. If your default had been even more general, impl<T, U> FooIt<T> for U, then impl<T> FooIt<T> for Bar would be a valid specialization.

I think you would need intersection impls, where you would explicitly add impl FooIt<Bar> for Bar to resolve the overlap. There's also a followup post on always applicable impls which seems to be the current plan, tracked in rust#48538.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.