Understanding Parameter Packs in Swift With a Real Example
How a small compiler error reveals a big evolution in the language.
Today I was writing some code for a client that was using PromiseKit, a third-party library similar to Combine for handling async operations. Basically PromiseKit is working with Promise objects, hence the name, and each Promise is something that will be fulfilled later on (useful for async operations) - similar to Future for Combine friends.
So in PromiseKit there is a function called when(..) that takes as parameter many Promises inside, and it appears to accept any number of Promises, similar to a variadic argument where you can pass as many Promises as you want.
The compiler error
Little did I know, until I added a sixth parameter, and the compiler was nagging about the when() function. It was giving me an error because it could only support up to 5 arguments.
So I took a look at the library and I saw this.
The reason the arguments are generic is because a promise can carry any type. E.g Promise<Int> or Promise<String>
The Naive Solution
Instinctively, my first thought was to contribute to the library by adding a new function with 6 generic arguments. But.. what if later we need 7?
But wait a second, this looks like repetitive code, it sounds overkill to have to implement a new function for each parameter count number.
Hello Parameter packs
This got me curious, so then I searched and I found out that from swift 5.9+ we are able to use a new feature called parameter packs (or variadic generics)
And all the code you see above could basically be replaced by the following:
Wow, that was my first reaction when I finished writing that, first of all it looks like it is not swift at all because of the new keywords. Well, let’s see this together.
First let’s analyze the function signature.
The when() is a generic function because we see the <> characters.
”<each U: Thenable>” declares a type parameter pack, a placeholder for one or more types. The number of parameters will be decided at the call site (inferred later from the function parameters). The
: Thenableconstraint ensures each type in the pack conforms to the Thenable protocol.“repeat each U“ is where the pack expansion happens, this allows the compiler to expect from 0 or more generic arguments as parameters on call site. For example if caller uses 3 parameters the equivalent without parameter packs would be “_genericParam1: U, _ genericParam2: V, _ genericParam3: W“.
The return is also using pack expansion. It expands into a
Promisewrapping a tuple of each promise's resolved type. For example, if you passPromise<Int>andPromise<String>, the return type becomesPromise<(Int, String)>."
Now that we have covered the function signature we can continue with the implementation. As you can see we are using
repeat voidPromises.append((each fulfilled).asVoid())
// Which is equivalent to
voidPromises.append((parameter1).asVoid())
voidPromises.append((parameter2).asVoid())
voidPromises.append((parameter3).asVoid())
....The rest is just PromiseKit internal implementation so it’s skipped from this article.
And that’s how powerful the compiler can be.
When Will You Need This?
Now you might be wondering, “how often will I actually need this?”
Honestly, in a typical iOS codebase, probably never. But if you are building a library, parameter packs can save you from maintaining dozens of overloads and your users from hitting arbitrary argument limits like I did.
If you are interested in learning more I would recommend taking a look at the following WWDC.
Thanks for reading, feel free to share your thoughts in the comment section see you in the next article 👋🚀
await nextArticleCodingWithKonsta()




