Variadic Types in Swift and How to Count Them
Rick van Voorden in yesterday’s Open Office hour told me about the fact that variadic types in Swift can, in fact, have an empty list of associated types.
A quick refresher of variadic types in Swift using parameter packs:
struct Box<each Dependency> {
init(_ deps: repeat each Dependency) {}
}
The each Dependency
phrase allows us to get e.g.
Box<Int>
, with one generic type argument;Box<Int, Int>
, with two;Box<String, Int, Double, Box<Int, Int>>
, with many – but also:Box< >
, with none.
Note the space in the empty case!
If you manually want to type this in Swift, you need a space there; Box<>
will make the compiler complain. And of course that derailed me in the past.
Now with a Box with a variadic amount of types in the generic clause, to get a (runtime) handle of the list types, you need to expose a static (on-the-type-level) reference like so:
extension Box {
static var dependenciesTypes: (repeat (each Dependency).Type) {
(repeat (each Dependency).self)
}
}
That will give a tuple with ()
for the empty box, and (Int.self, Int.self)
for the Box<Int, Int>
case.
With that handle on the types, you can count them:
func count<each D>(_ b: Box<repeat each D>) -> Int {
let dependenciesTypes = type(of: b).dependenciesTypes
var count = 0
for _ in repeat each dependenciesTypes {
count += 1
}
return count
}
And when you can count them, you can also check for emptiness:
func isEmpty<each D: Dependency>(_ b: Box<repeat each D>) -> Bool {
let dependenciesTypes = type(of: b).dependenciesTypes
for _ in repeat each dependenciesTypes {
return false
}
return true
}
This static, computed property is required because Box<Int, Int>.self
has no other means of getting to the “<Int, Int>
” part that you write out-of-the-(sorry)-box.
That’s not special. By default, types don’t expose just their generic parts:
struct Foo<T> {}
_ = Foo<Int>.self.T // 🛑 Type 'Foo<T>' has no member 'T'
We use typealiases for that in Swift:
struct Bar<T> {
typealias ExposedT = T
}
_ = Bar<Int>.self.ExposedT // Int.self
With typealiases, you can also get a hold of the info on the type level:
extension Box {
typealias Dependencies = (repeat (each D).Type)
}
func isEmpty2<each D>(_ b: Box<repeat each D>) -> Bool {
let dependenciesTypes = type(of: b).AllDependencies
return dependenciesTypes == type(of: ())
}
The content of Box< >.AllDependencies
is an empty tuple, ()
, but the type of the empty tuple is Void.self
, which we can also write type(of: ())
. I chose that for reasons of symmetry.
Note that this doesn’t help with counting. You need the for
-in
-repeat each
loop for that, and I believe you cannot get a loop to compile with the result of type(of: b).AllDependencies
which is (repeat (each Dependency).Type).Type
, aka a tuple type. You need to iterate over/unpack the tuple value. The static property above is the best bet there as far as I know.
Finally, we can check for emptiness by letting the compiler decide, instead of doing runtime checks in function bodies:
func isEmpty3(_ b: Box< >) -> Bool {
return true
}
@_disfavoredOverload
func isEmpty3<each D>(_ b: Box<repeat each D>) -> Bool {
return false
}
Using @_disfavoredOverload
, we instruct the compiler to favor the empty box variant if possible – and by definition, it’s not possible in any case but the one where Box< >
is passed.
This “trick” can be used to check whether any tuple is empty or not, too.

I tried that in compiler explorer to see whether the last variant would actually help the compiler get closer to a constexpr
in Swift or not. The result, without -Onone
optimizations turned off, is indeed a more efficient assembly output.
But with optimizations turned on, it’s the same!
Check it out on Compiler Explorer: https://godbolt.org/z/4aTrMj65T
You don’t need to read and understand the assembly to see that the left and right side produce similar output. Either way, it’s good to know that the compiler is smart enough to get to the same best possible result in both variants.