Hehey yeah it's the most beloved topic: specialization.
Up front, I want to say that I don't have deep knowledge about how the rust compiler really works, but it was too tempting to write this blog post, so yeah.. you are free to call me stupid after this.
Why I even need specialization
Like some other people... I figured that I need it. But I don't really need it for any cool optimization or "special implementation", instead I want to do some sort of type level Set, and that has lead to some interesting thoughts.
So the idea is simple. You have ()
and (Down, T)
as the two implementors for Set
, where Down: Set
. This will enable me to have a value like (((), "hello"), 42)
and then get "the most outside &str
" or "the most outside i32
". This, however, requires me to have two conflicting implementations, because I can't say that SetT = SearchT
weighs more than Down: Has<T>
.. unless, of course, with specialization involved. But that is unsound, so I sought help, of which you can find the result here. Long story short, I've reached the limits of what the propsed version there enables and now need real specialization, but also got an interesting idea from that solution.
Now what was the problem with specialization again?
Right... the problem with specialization and lifetimes. The most basic example from aturon's blog is the following:
trait Bad1 {
fn bad1(&self);
}
impl<T> Bad1 for T {
default fn bad1(&self) {
println!("generic");
}
}
// Specialization cannot work: trans doesn't know if T: 'static
impl<T: 'static> Bad1 for T {
fn bad1(&self) {
println!("specialized");
}
}
fn main() {
"test".bad1()
}
trans
can't tell if the more specialized implementation is satisfied.
The Idea
While others have suggested to make trans
smarter, I have come up with a different idea. At least to me, it seems like while trans
doesn't know about lifetimes, typeck
does, and thus also the part that is responsible for picking traits.
The idea I have in mind is simple:
Because monomorphization is already capable of doing different stuff on trait implementations, e.g.
trait A {
fn get_name(&self) -> &'static str;
}
impl A for i32 { .. }
impl A for i16 { .. }
fn function(a: &impl A) {
let _ = a.get_name(); // this could call two different methods
}
what is stopping us from extending that then?
What I mean by that is having two mutually exclusive sets represented by a specialized trait, where we already categorize the type into "this specialization applies" and "doesn't apply" at the time of typeck
. For my experiments, I have named this trait Is<T>
.
Is<T>
now has an associated type, called Really
, which is only set to either Yes
or No
.
Those are for now implemented with the current #![feature(specialization)]
, but I don't know how safe that makes it as I'm unsure where the picking happens in that case.
Coming back to the Set
and Has<T>
example, it looks like this and can successfully differentiate between &'static str
and &'a str
.
Cool - now have I just solved the specialization problem? I don't know, but my brain dug out some other issue I should try to come over: Specialization and lifetime dispatch #40582
Show stopper: I didn't achieve it.
.....buuuuuuuuuuuut I still think it's doable!
Now what was the blocker?
In my experiment the compiler for some reason inferred that when I enforce T: 'static
, the lifetime 'a
in &'a T
must also be 'static
- at least that's what I think is happening. A shorter reprod of this mistake can be observed here.
Prologue
Now, after this, I wonder:
- Does the implied
'static
bounds have any sense I've overseen or can it be removed? - With the implied
: 'static
removed, does this idea really work? - Does at least the general idea work after some more changes, e.g. putting some of the choosing stuff from
trans
more intotypeck
?
More shenanigans I've found while experimenting:
- playground The reason for why
T
should be: 'static
is on the wrong span, one type argument later. - playground Changing the associated type to an associated
bool
const together with#![feature(associated_const_equality)]
and#![feature(generic_const_exprs)]
makes the compiler error
I have no clue whether either of these issues are known, or how I could possibly find out if they are. The only thing that came to mind was a simple github issue search, which however didn't yield any results. So I'm instead just gonna put them here and see what happens .