Modern way to vend specializations of a generic type?

it’s quite a common occurrence where i will have a generic type that is only intended to be used with a handful of known types.

Sources/Events/Event.swift

@frozen public
struct Events<ID>
{
    ...
}

Sources/App/Main.swift

import Events

let _:Events<String>
let _:Events<Int32>

(in this case, Events<T> is not particularly amenable to refactoring into a protocol with multiple non-generic conforming types, it really is shaped more like a generic container.)

i’m aware of a couple terrible ways to fix this problem:

  1. we can make all of Events<T>’s implementation @inlinable public, @frozen public, etc. (it should be self-evident why this is bad practice.)

  2. we can use the @_specialize attribute, which must be applied to every declaration in Events<T> (instead of at the type level), and which i’ve heard through the grapevine is deprecated/not working reliably/generally not recommended.

Events<T> is not expected to be used outside its home package; its performance for types T besides String and Int32 is unimportant to me.

what’s the recommended way to vend a generic type with a handful of known specializations?

4 Likes

Did you sort this one out? Running in to the same thing….

no, i just resort to drenching the entire type with @inlinable. i rely on hyper-modularization to preserve some semblance of access control. if the amount of API and specializations are very low, i will sometimes also “manually specialize” by wrapping in a concrete type and parroting all the members.

perhaps @Slava_Pestov might have some pointers?

Yeah, I would really not want to do that. Looked at trying to create a factory method wrapper on the "inside" of the module to see if I could get specialisation to kick in, but no success - after a decent refactoring to have two duplicated implementations that were type specific to now instead use fully generic code, it just drove off a performance cliff now with 8-10x difference (seems it started to copy a ton, need to dig some further - but basically we have a similar setup with just a handful, say 5-10, of types that will use the generic code)...

For anyone ending up here later, I fixed it by refactoring such that the function that was part of my generic type, was broken out as a freestanding new generic function with the usual inline annotations, adding a new protocol that defined that signature, and adding another constraint on this new protocol to the original generic types T. Then adding trampoline implementations for my concrete types that just called the new inlinable generic function. This made it specialised and things are back fundamentally to where they were...

1 Like