API design question - generics and protocols

The new randomElement(using:) method is declared as:

func randomElement<T>(using generator: inout T) -> Element? where T : RandomNumberGenerator

I have used protocols for a long time in Obj-C, but am still wrapping my head around generics. What are the reasons or advantages for declaring the API using a constrained generic type this as opposed to just using the protocol name, as below?

func randomElement(using generator: inout RandomNumberGenerator) -> Element?

Thank you for any help in understanding this.

I'll let real language experts provide the definitive answer, but I'll give a shot anyway, in order to check my little muscles:

One of the differences, and I think the one that has driven the design here, is about runtime performance: a generic method can be specialized by the compiler, which means that the compiler can emit as many versions of the generic method as there are statically known types that are used as a generic argument. Each one of those specialized versions can be efficiently optimized, inlined, etc, with all the brute force of a modern omniscient compiler. Conversely, the method that accepts the protocol can not be specialized: protocol values have to be accessed indirectly, through their "witness table" (unlike ObjC message passing, witness tables are more akin to C++ virtual tables). A witness table not only adds a little but unavoidable overhead, but it also hinders optimization: compiler doesn't know what's hidden behind this indirection.

Now look at the randomElement() method (the one without arguments, which is expected to be the most used). It delegates its job to randomElement(using:), by passing an instance of the default RandomNumberGenerator. This is statically known information. By declaring randomElement(using:) generic, and allowing specializations, the standard library allows the compiler to provide the most efficient implementation it can for randomElement().

Take this with a big grain of salt: I may have written total BS. Just like you I'd like to see this hypothesis confirmed or infirmed.

1 Like

In theory the compiler can specialize a function that takes a protocol value as well as long as it can see both the call site and the implementation, though I don't know if we've implemented that. The problem is more something like this:

func evil(generator: inout RandomNumberGenerator) {
  generator = SystemRandomNumberGenerator.default

var gen = MyRandomNumberGenerator()
evil(&gen) // the compiler will actually stop you from doing this

That is, if you use a protocol value here, the implementation is allowed to change the type out from under you. If you use a generic, then you know it can't change.

In practice, no one is going to do this, but the compiler can't know that. That's why generics are useful: they preserve and propagate type information from the call site.


Thanks for our :muscle: