Specialization macro experiments


#1

Now that specialization has landed in nightly, I’ve been eager to try it out and see what can be built on top of it. The ability to inspect the type behind a generic bound at monomorphization time and write logic specific to the type or additional generic bounds is exciting for more than just the obvious performance enhancements! It also allows one to design APIs that are less burdensome on the user; trait bounds can become “optional”, and we can now move performance-sensitive types and traits behind singular generic abstractions.

So I’ve created some macros to experiment with exposing some potentially common patterns that specialization enables, and am looking for some feedback.

The crate documentation mentions the macros and some example usages. The tests also include some more examples. It consists of two things right now:

  1. A shorthand specialize! { match } macro for writing specialized implementations of a single function.
  2. A constrain!() macro that adds additional trait bounds (or binds a concrete type) to a variable of generic type.

And some of my own notes and observations…

  1. The syntax is somewhat awkward all around. A lot of this is due to macro parsing limitations, as well as just a focus more on semantics first before fixing up the syntax into something that feels more natural.
  2. specialize! {} is probably too complex and convoluted for real use. Yes, it saves a lot of typing compared to writing out the traits and impls manually, but I’m not sure that it’s worth it in the end. Maybe it can be better given a different syntax?
  3. constrain!() seems potentially useful. The Debug case is a common one for me, even just when trying something out and I want to print something without having to add : Debug to 5 different generic bound chains. The concrete type Any behaviour is neat too, and even allows you to move the value.
  4. constrain!()'s need to specify an unused concrete fallback type is very very unfortunate.
    • I’m not sure how best to tackle that, or if it’s even possible to remove that requirement. You could make specific hard-coded fallbacks for some of the more common traits (like Debug).
    • Maybe just write specialized versions like constrain_debug!(v)?
    • Perhaps it can be used to create nicer APIs more along the lines of fn if_debug<T, F: FnOnce(&Debug)>(t: &T, f: F). You’ll potentially lose static dispatch in that case if LLVM can’t devirtualize the closure parameter though (is that even something it could do?)
  5. is constrain!() missing a way to name the type it produces (and would that be useful)? It mostly just relies on type inference to work.
  6. is it possible to expose a match functionality similar to specialize!{} that works more naturally like constrain!()? (i.e. allows inline expressions rather than using an isolated function scope). You could just use a chain of constrain!() else ifs instead though…

So yeah, any comments, suggestions, ways to improve what’s happening here, etc. would be really great! Even just hearing from anyone who’s been toying with specialization - and how and why - would be nice too!