stable specialization in Rust
with apologies to Rust maintainers
it is a well-known fact that Rust standard library uses specialization internally.
over the years, there were various attempts to observe this behaviour in user code
and thus achieve ersatz specialization on stable.
for example, removed Clone specialization on arrays
was used in such a way by a high-profile library.
the problem with this trick is that specializations are not guaranteed, so it might break over Rust update (as happened there). but there is an exception.
before we proceed, I would like to note that despite everything I’m about to say, I do not endorse actually using this trick in serious code.
that being said...
there’s a method on iterators called .fuse().
it ensures that after the iterator runs out of values, it will always return None,
and won’t panic, or suddenly return Some(_) again, or do something else weird.
as an optimization, if the original iterator implemented FusedIterator,
.fuse() is free and will just always proxy the calls without any other logic.
notably, this is guaranteed by the
Iterator::fuse() docs:
Note that the
Fusewrapper is a no-op on iterators that implement theFusedIteratortrait.
and by the
FusedIterator docs:
If the iterator is already fused, the additional
Fusewrapper will be a no-op with no performance penalty.
this is the only case I know of a specialization that is guaranteed to happen (and I actually went over all of them and checked).
thus, I believe it is possible to observe specialization on stable Rust relying only on documented behaviour.
let’s use a function that checks if a type implements Send as an example:
rust (playground) pub fn is_send<T: ?Sized>() -> bool {
struct Checker<'a, T: ?Sized>(&'a mut u8, PhantomData<T>);
impl<T: ?Sized> Iterator for Checker<'_, T> {
type Item = ();
fn next(&mut self) -> Option<()> {
// count amount of times we’ve been called
*self.0 += 1;
// iterator always returns `None`, so `Fuse`
// should stop calling it after the first call...
None
}
}
// ...unless it’s already fused, which is true iff `T: Send`
impl<T: ?Sized + Send> FusedIterator for Checker<'_, T> {}
let mut counter = 0;
let mut it = Checker(&mut counter, PhantomData::<T>).fuse();
// advance the iterator twice to check if `.fuse()` was noop
it.next();
it.next();
// if we’ve been called twice, `.fuse()` proxied the second call,
// so `T` must implement `Send`
counter == 2
}
assert!(is_send::<u8>());
assert!(!is_send::<*const u8>());unfortunately, I don’t think it’s usable in const contexts,
which prevents us from lifting it to type level even with generic_const_exprs.
it does potentially mean that generic_const_exprs and const traits taken together effectively allow specialization.