Specialization with Associated Types

I've got a trait with an associated type that I've got a default blanket impl for along with a specialized impl; the two impls have a different value for the associated type.

When I use the associated type in a trait bound on a function, typechecking fails as expected when the actual associated type doesn't match what the bound is asking for (the error message shows the expected type and the actual type). However, when I give the function a type that does have the expected type for the trait's associated type, it fails to typecheck with an error message that doesn't show what the associated type actually is ("expected struct Yes; found associated type <SomeFoo as FooFilter>::IsNotClonable").

I have a minimized example here: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=b9014ecaf065b73d813ea04aa0445035

#![feature(specialization)]
#![allow(incomplete_features)]

// Minimized:
struct Yes; struct No;

trait Foo { }

struct SomeFoo; impl Foo for SomeFoo { }
#[derive(Clone)] struct ClonableFoo; impl Foo for ClonableFoo { }

trait FooFilter { type IsNotClonable; }
impl<T: Foo> FooFilter for T { default type IsNotClonable = Yes; }
impl<T: Foo + Clone> FooFilter for T { type IsNotClonable = No; }

fn unclonable_foos_only<F: FooFilter<IsNotClonable = Yes>>(_foo: F) { }

fn main() {
    // Correctly doesn't typecheck:
    // ```
    // |   unclonable_foos_only(ClonableFoo);
    // |   ^^^^^^^^^^^^^^^^^^^^ expected struct `Yes`, found struct `No`
    // ```
	unclonable_foos_only(ClonableFoo);
	
	// Also doesn't typecheck:
	// ```
    //   |     unclonable_foos_only(SomeFoo);
    //   |     ^^^^^^^^^^^^^^^^^^^^ expected struct `Yes`, found associated type
    //   |
    //   = note:       expected struct `Yes`
    //           found associated type `<SomeFoo as FooFilter>::IsNotClonable`
    // ```
	unclonable_foos_only(SomeFoo);
}

Is this the expected behavior?

Nevermind; I missed the section on type checking hazards in the RFC.

Though, I'm not super clear on why the solution is to treat the associated type as opaque.

I understand that normalizing the associated type to what the what the least specific impl says it is is problematic (as the example in the RFC shows) and having the type checker say that the associated type in such a situation is "one of" the types specified by the base impl + more specific impls (i.e. Box<T> or bool in the example) could becomes problematic because (I think?) downstream crates could provide more specific impls, widening the set of types that the associated type must be "one of".

But, it seems like you could make it so that the associated type isn't opaque when there's an impl containing a final (non-default) value for the associated type.

I guess this would get tricky once you consider impls in downstream crates (though, as a sidenote, it seems like this is a problem anyways? for example:

// Crate A
trait Foo { type Bar; }
impl<T> Foo for T { default type Bar = (); }

// Crate B
trait MyTrait { }
impl MyTrait for u8 { }
impl<T: MyTrait> A::Foo for T { type Bar = ((), ());  }

// Crate C
trait AnotherTrait { }
impl AnotherTrait for u8 { }
impl<T: AnotherTrait> A::Foo for T { type Bar = ((), (), ()); } // Overlaps with the impl in crate B!

) but even then I think you could carve out an exception for associated types that end up being final within a specialized impl that exists in the crate that the trait they're in is defined in.

This'd be useful for my use case, but at that point it's very likely too niche/complicated to really be of use.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.