some and any keyword in Swift
Discover the enhanced version of the article, complete with refined syntax highlighting and all the latest updates, now available on my personal blog! Your feedback and engagement are greatly appreciated and help fuel future content:
How many times have you encountered the following compilation error:
Protocol ‘Type’ can only be used as a generic constraint because it has Self or associated type requirements
…….. I have, a lot !
Swift 5.7 introduced 2 new keywords: some
and any
that helps us get rid of this error in the most neat manner
I meant neat manner because prior to Swift 5.7, we could have gotten rid of that error without the use of some
and any
using generics:
import Foundation
protocol StorageMechanism {
associatedtype TypeOfStorage
}
func implementingStorageFirst(_ mechanism: StorageMechanism) {
// ❌ Will not compile since StorageMechanism
// has associatedtype defined inside it
}
func implementingStorageSecond<T: StorageMechanism>(_ mechanism: T) {
// ✅ Will Compile since we are now using generics
}
func implementingStorageThird(_ mechanism: some StorageMechanism) {
// ✅ Will Compile since we are now using some keyword
}
But as you dive deep into generics with Protocols, you’ll realise some
and any
are the syntactic sugar that we needed all this time
The main focus of this article is to explain both these keywords through similarities and differences between them. I will be using one of SwiftUI’s protocol View
whose declaration primarily looks like:
public protocol View {
associatedtype Body : View
}
Similarities
Both some
and any
can be used with Protocol that have associatedtype in it (also called as generic protocols)
Consider the following example:
import SwiftUI
// 1. ❌ Will not compile
var var1: View = Text("Sample Text")
// 2. ✅ Will compile
var var2: some View = Text("Sample Text")
// 3. ✅ Will Compile
var var3: any View = Text("Sample Text")
Reasons for the above:
- Compilation failed since
View
hasassociatedtype Body: View
inside it - Compilation succeeds since now we have declared the type with
some
keyword which acts as opaque type - Compilation succeeds since now we have declared the type with
any
keyword which acts as existential type
Differences
The real fun starts when you look at the actual differences between the two of them since above code does not clearly states which should be used when
Homogeneous vs Heterogeneous collection behaviour
some
only allows homogeneous collection, meaning only similar conforming type are allowed inside the collection. Meanwhile any
allows heterogeneous collection meaning any conforming type is allowed inside the collection
Consider the following example:
import SwiftUI
// 1. ✅ Will compile
var var1: [some View] = [ Text("Sample Text"), Text("Sample Text") ]
// 2. ❌ Will not compile
var var2: [some View] = [ Text("Sample Text"), Label("Sample Text", systemImage: "bolt.fill") ]
// 3. ✅ Will Compile
var var3: [any View] = [ Text("Sample Text"), Label("Sample Text", systemImage: "bolt.fill") ]
Reasons for the above:
- Compilation succeeds since all the
View
’s conforming type that are being added to the collection are of same type; andsome
is good with that 😎 - Compilation failed since now we have 2 different conforming types inside the same collection;
some
gets angry 😤 - Compilation succeeds since
any
is unbothered whether or not the conforming types that are being added to the collection are of same type or not 😴
Similar return type
some
only allows same conforming type to be returned; any
however is generous and allows any conforming type to be returned
Consider the following example:
import SwiftUI
// 1. ✅ Will compile
var var1: some View {
let random = Int.random(in: 1...5)
if random % 2 == 0 {
return Text("Even")
}
else {
return Text("Odd")
}
}
// 2. ❌ Will not compile
var var2: some View {
let random = Int.random(in: 1...5)
if random % 2 == 0 {
return Text("Even")
}
else {
return Label("Sample Text", systemImage: "bolt.fill")
}
}
// ✅ Will compile
var var3: any View {
let random = Int.random(in: 1...5)
if random % 2 == 0 {
return Text("Even")
}
else {
return Label("Sample Text", systemImage: "bolt.fill")
}
}
Reasons for the above:
- Compilation succeeds since we’ll always be returning the same conforming type irrespective of the value of random at runtime;
some
approves of it 👍 - Compilation failed since now different conforming types are being returned;
some
rejects it 👎 - Compilation succeeds since
any
is unbothered whether or not we’re returning same or different conforming types; typicalany
🥱
Now that we are done with the similarities and differences, it’s time for the verdict:
You should always start with some keyword first and if that does not fit your generics use case, switch to
any
keyword
That is because any
relies on a mechanism in programming language called Type Erasure which essentially hides specific type information from code that doesn’t need to concern itself with those types. I won’t be covering that here since that will just add lengths to the article. But you should definitely read about it
That’s it, folks! Happy Coding!
You can connect with me on LinkedIn 👱🏻 or can get in touch with me via other channels 📬