Opaque vs Existential vs Concrete Type Hidden Ambiguity in Swift
#Swift, #iOS App Development, #Software Engineering
Came across an interesting behavior in Swift which I found not only curious but also not obvious and VERY error-prone.
This is related to how Swift would prioritise implementation when dispatching function calls.
Tested on Xcode 15.3 Swift 5.10
Some vs Any
some
indicates an opaque type which known to the compiler at compile time. Type that conforms to a protocolany
indicates an existential type – a box whose contained value has a type which is not known to the compiler at compile time. The contained value type conforms to a protocol
Example 1. Some vs Any
Here is a simple example:
func foo(_ param: any Equatable) {
print("I'm any equatable")
}
func foo(_ param: some Equatable) {
print("I'm some equatable")
}
if we call
foo(true)
The result would be
I'm any equatable
Example 2. Generic Type vs Any
The implementation with some
keyword is identical to an old school style generic implementation:
func foo<T: Equatable>(_ param: T) {
print("I'm generic<T> equatable")
}
func foo(_ param: any Equatable) {
print("I'm any equatable")
}
If we run it again, we will see the same result – Swift will ignore the implementation with the compile-time type check:
foo(true)
The result would be:
I'm any equatable
Example 3. Some vs Any vs Concrete Type
However, things would be different If we provide a concrete type implementation:
func foo(_ param: any Equatable) {
print("I'm any equatable")
}
func foo(_ param: some Equatable) {
print("I'm some equatable")
}
func foo(_ param: Bool) {
print("I'm bool")
}
Run:
foo(true)
The result would be :
I'm bool
What's Bad in This Design
First of all, it's easy to confuse. The mnemonic rule could be to think of a foo(:Bool) as if it is overriding foo(:Equatable) because Bool conforms to Equatable.
I would love to hear your ideas on some
vs any
mnemonic rules here.
Secondly, it's controversial.
- In the case of
some
vsany
protocols, the compiler disregards the implementation with the type known at compile time and picks the implementation with a runtime check. - In the case of a concrete type it prefers the implementation with a type, known at compile time.
Thirdly, it's error-prone. The compiler doesn't even produce any warnings for clearly ambiguous some
vs any
implementations. It's fairly easy to accidentally override something in an extension without even knowing about it.
Comments