Something That Confused Me About Rust Specialization
I recently read through RFC 1210 to understand Rust's proposed specialization features. I found the example given in Going down the rabbit hole confusing, so I started writing a question to post to IRLO. In the course of writing up my question I also figured out the solution, so now I'm posting my solution here instead.
The example is below. It's meant to demonstrate how you can get into trouble with specialization that depends only on lifetimes, and how this can affect user code despite not directly involving any code the user wrote.
////////////////////////////////////////////////////////////////////// // Crate marker ////////////////////////////////////////////////////////////////////// trait Marker {} impl Marker for u32 {} ////////////////////////////////////////////////////////////////////// // Crate foo ////////////////////////////////////////////////////////////////////// extern crate marker; trait Foo { fn foo(&self); } impl<T> Foo for T { default fn foo(&self) { println!("Default impl"); } } impl<T: marker::Marker> Foo for T { fn foo(&self) { println!("Marker impl"); } } ////////////////////////////////////////////////////////////////////// // Crate bar ////////////////////////////////////////////////////////////////////// extern crate marker; pub struct Bar<U>(U); impl<U: 'static> marker::Marker for Bar<U> {} ////////////////////////////////////////////////////////////////////// // Crate client ////////////////////////////////////////////////////////////////////// extern crate foo; extern crate bar; fn main() { // prints: Marker impl 0u32.foo(); // prints: ??? // the relevant specialization depends on the 'static lifetime bar::Bar("Activate the marker!").foo(); }
(I changed the T
variables in crate bar to U
to make it easier to disambiguate them below.)
The part that took me a while to work out was how the call bar::Bar("Activate the marker!").foo()
depends only on the 'static
lifetime.
Why I was confused🔗
There are two impls that are applicable:
impl<T> Foo for T
withT = Bar<U>
impl<T: marker::Marker> Foo for T
withT = Bar<U>
becauseBar<U>: Marker
In this case, the second impl is the more specific because T: Maker>
is more specific than T
.
It seems like the right thing is that bar::Bar("Activate the marker!").foo()
should print Marker impl
.
But according to the RFC, my reasoning here is flawed. What's wrong?
The solution🔗
In listing the two impls that are applicable above, I ignored one constraint.
Bar<U>
does not impl Marker
for all U
, but only if U: 'static
.
If that impl applies, there would be a clear answer as to which is more specific, since T: Marker
is more specific than just T
.
But, since the second impl only applies is U: 'static
, that means the difference between which impl we can use depends solely on whether U: 'static
.
How the RFC solves this🔗
It's best to go read the RFC, but the RFC argues that specialization based on lifetimes is not something we want to try to do.
Under the section What this means for the programmer, the suggested strategy is to "just ignore lifetimes for specialization."
I think it might be better to say "do not consider specializations that depend on lifetimes."
Since we have two impls in the example above, but one is only applicable for certain lifetimes, the specialization logic would ignore the second possibility and instead bar::Bar("Activate the marker!").foo()
would print Default impl
.
Additionally, the RFC proposes to print a warning when it detects this case happening that explains why the second specialization was not chosen.