The some
and any
keywords are not new in Swift. The some
keyword was introduced in Swift 5.1 whereas the any
keyword was introduced in Swift 5.6. In Swift 5.7, Apple makes another great improvement on both of these keywords. We can now use both of these keywords in the function’s parameter position!
func doSomething(with a: some MyProtocol) {
// Do something
}
func doSomething(with a: any MyProtocol) {
// Do something
}
This improvement not only made the generic functions look a lot cleaner, but also unlocked some exciting new ways to write generic code in Swift. Spoiler alert – we can now say goodbye to the following error message:
protocol can only be used as a generic constraint because it has Self or associated type requirements
Wanted to know more? Read on!
First Things First
Before getting into the details, let’s define the protocol that we will be using throughout this article.
protocol Vehicle {
var name: String { get }
associatedtype FuelType
func fillGasTank(with fuel: FuelType)
}
After that, we will define a Car
and a Bus
struct that conforms to the Vehicle
protocol and each of them will require different kinds of fuels.
struct Car: Vehicle {
let name = "car"
func fillGasTank(with fuel: Gasoline) {
print("Fill \(name) with \(fuel.name)")
}
}
struct Bus: Vehicle {
let name = "bus"
func fillGasTank(with fuel: Diesel) {
print("Fill \(name) with \(fuel.name)")
}
}
struct Gasoline {
let name = "gasoline"
}
struct Diesel {
let name = "diesel"
}
Notice that the fillGasTank(with:)
function’s parameter data type of Car
and Bus
are not the same, Car
requires Gasoline
whereas Bus
requires Diesel
. That is why we need to define an associated type called FuelType
in our Vehicle
protocol.
With that out of the way, let’s dive right into the details, shall we?
Understanding the “some” Keyword
The some
keyword was introduced in Swift 5.1. It is used together with a protocol to create an opaque type that represents something that is conformed to a specific protocol. When used in the function’s parameter position, it means that the function is accepting some concrete type that conforms to a specific protocol.
At this stage, you might be wondering, aren’t we already able to do that?
In fact, you are right. Using the some
keyword in the function’s parameter position is exactly the same as using the angle brackets or a trailing where
clause at the function signature.
// The following 3 function signatures are identical.
func wash<T: Vehicle>(_ vehicle: T) {
// Wash the given vehicle
}
func wash<T>(_ vehicle: T) where T: Vehicle {
// Wash the given vehicle
}
func wash(_ vehicle: some Vehicle) {
// Wash the given vehicle
}
When we use the some
keyword on a variable, we are telling the compiler that we are working on a specific concrete type, thus the opaque type’s underlying type must be fixed for the scope of the variable.
var myCar: some Vehicle = Car()
myCar = Bus() // 🔴 Compile error: Cannot assign value of type 'Bus' to type 'some Vehicle'
One interesting point to be aware of is that assigning a new instance of the same concrete type to the variable is also prohibited by the compiler.
var myCar: some Vehicle = Car()
myCar = Car() // 🔴 Compile error: Cannot assign value of type 'Car' to type 'some Vehicle'
var myCar1: some Vehicle = Car()
var myCar2: some Vehicle = Car()
myCar2 = myCar1 // 🔴 Compile error: Cannot assign value of type 'some Vehicle' (type of 'myCar1') to type 'some Vehicle' (type of 'myCar2')
With that in mind, we will have to follow the same rule when using it with an array.
// ✅ No compile error
let vehicles: [some Vehicle] = [
Car(),
Car(),
Car(),
]
// 🔴 Compile error: Cannot convert value of type 'Bus' to expected element type 'Car'
let vehicles: [some Vehicle] = [
Car(),
Car(),
Bus(),
]
Same goes to the underlying return type of a function.
// ✅ No compile error
func createSomeVehicle() -> some Vehicle {
return Car()
}
// 🔴 Compile error: Function declares an opaque return type 'some Vehicle', but the return statements in its body do not have matching underlying types
func createSomeVehicle(isPublicTransport: Bool) -> some Vehicle {
if isPublicTransport {
return Bus()
} else {
return Car()
}
}
That’s all for the some
keyword. Let’s head over to the any
keyword and see what are the differences between them.
Understanding the “any” Keyword
The any
keyword was introduced in Swift 5.6. It is introduced for the purpose of creating an existential type. In Swift 5.6, the any
keyword is not mandatory when creating an existential type, but in Swift 5.7, you will get a compile error if you failed to do so.
let myCar: Vehicle = Car() // 🔴 Compile error in Swift 5.7: Use of protocol 'Vehicle' as a type must be written 'any Vehicle'
let myCar: any Vehicle = Car() // ✅ No compile error in Swift 5.7
// 🔴 Compile error in Swift 5.7: Use of protocol 'Vehicle' as a type must be written 'any Vehicle'
func wash(_ vehicle: Vehicle) {
// Wash the given vehicle
}
// ✅ No compile error in Swift 5.7
func wash(_ vehicle: any Vehicle) {
// Wash the given vehicle
}
As explained by the Apple engineers, an existential type is like a box that contains something that conforms to a specific protocol.
As illustrated in the image above, the main difference between an opaque type and an existential type is the “box”. The “box” enables us to store any concrete type within it as long as the underlying type conforms to the specified protocol, thus allowing us to do something that an opaque type doesn’t allow us to do.
// ✅ No compile error when changing the underlying data type
var myCar: any Vehicle = Car()
myCar = Bus()
myCar = Car()
// ✅ No compile error when returning different kind of concrete type
func createAnyVehicle(isPublicTransport: Bool) -> any Vehicle {
if isPublicTransport {
return Bus()
} else {
return Car()
}
}
The best part is that, in Swift 5.7, we can now use the any
keyword for protocols with associated types! This means that creating a heterogeneous array using a protocol with associated types is no longer a limitation!
// 🔴 Compile error in Swift 5.6: protocol 'Vehicle' can only be used as a generic constraint because it has Self or associated type requirements
// ✅ No compile error in Swift 5.7
let vehicles: [any Vehicle] = [
Car(),
Car(),
Bus(),
]
How cool is that? 😃
This improvement not only eliminated the “protocol can only be used as a generic constraint because it has Self or associated type requirements” error, but it also makes accomplishing dynamic dispatch on protocol with associated types a lot more straightforward! But that will be an article for another day.
Pro Tip:
Find out how easy it is to achieve dynamic dispatch on protocol with associated types here.
The “any” Keyword Limitations
As good as it might seem, the existential type created using the any
keyword still has its own limitations. One major limitation is that we cannot use the ==
operator to compare 2 instances of existential type.
// Conform `Vehicle` protocol to `Equatable`
protocol Vehicle: Equatable {
var name: String { get }
associatedtype FuelType
func fillGasTank(with fuel: FuelType)
}
let myCar1 = createAnyVehicle(isPublicTransport: false)
let myCar2 = createAnyVehicle(isPublicTransport: false)
let isSameVehicle = myCar1 == myCar2 // 🔴 Compile error: Binary operator '==' cannot be applied to two 'any Vehicle' operands
let myCar1 = createSomeVehicle()
let myCar2 = createSomeVehicle()
let isSameVehicle = myCar1 == myCar2 // ✅ No compile error
If you think about it, this actually kind of makes sense. As mentioned earlier, an existential type can have any concrete type stored in its “box”. To the compiler, an existential type is just a “box”, it has no idea what’s inside the box. Therefore, it is not possible for the compiler to make a comparison when it cannot guarantee that the content of the “box” has the same underlying concrete type.
Another limitation that you should be aware of is that the existential types are less efficient than the opaque types (created using the some
keyword). Donny Wals has a great article that discusses this in much detail, I highly recommend you to check it out.
Therefore, even though Apple has made a lot of improvements on the any
keyword, it is still recommended to use the some
keyword if the opaque types can get the job done.
Wrapping Up
The improvements to the any
and some
keyword in Swift 5.7 is definitely a welcome one. On one hand, it drastically improved the syntax and readability of our generic code. On the other hand, it opens up new ways for us to write generic code in a much more efficient manner.
I hope this article gives you a good look into the any
and some
keyword. Feel free to follow me on Twitter and subscribe to my monthly newsletter so that you won’t miss out on any of my upcoming articles.
Thanks for reading. 👨🏻💻
Related WWDC Sessions
👋🏻 Hey!
While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.
- ✨ Bartender: Superpower your menu bar and take full control over your menu bar items.
- ✨ CleanShot X: The best screen capture app I’ve ever used.
- ✨ PixelSnap: Measure on-screen elements with ease and precision.
- ✨ iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.