max’s place
who’s max? atom feed this site is best viewed without JavaScript

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 Fuse wrapper is a no-op on iterators that implement the FusedIterator trait.

and by the FusedIterator docs:

If the iterator is already fused, the additional Fuse wrapper 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.