What is the “any” keyword in Swift?

Published on: March 15, 2022

This article has been updated for Swift 5.7

With Swift 5.6, Apple added a new keyword to the Swift language: any. As you'll see in this post, usage of the any keyword looks very similar to how you use the some keyword. They're both bput in front of protocols, and they both tell us something about how that protocol is used. Once you dig deeper into what any means, you'll find that it's very different from some. In fact, you might come to the conclusion that any is somewhat of the opposite of some. In this post, you will learn everything you need to know about the any keyword in Swift as well as existentials, and what they are.

Let’s dive right into the any keyword by taking a look at its intended use in a very simple example:

protocol Networking {
    func fetchPosts() async throws -> [Post]
    // ...
}

struct PostsDataSource {
    let networking: any Networking
    // ...
}

💡Tip: If you’re not familiar with Swift’s some keyword or need a refresher, check out this post on Swift’s some keyword.

While the any keyword might look similar to the some keyword in the sense that both are used in front of a protocol, and sound like they convey a message similar to “I don’t care what’s used for this type as long as it conforms to this protocol”, they’re really not the same at all. To understand their differences, we need to take a look at what existentials are in Swift.

Understanding what an existential is in Swift

While some allows us to write code that more or less ignores, or discards, a protocol’s associated type and/or Self requirement while expecting that every returned object in a function that returns some Protocol has the same concrete type, the any keyword simply annotates that a given type is a so-called existential. While you might not know what an existential is, you've probably seen them used. For example, if we look at the "old" way of writing the PostsDataSource struct that you just saw, it would look as follows:

struct PostsDataSource {
    let networking: Networking
    // ...
}

Note that all I did is remove the any keyword. The Networking object that we use is an existential. This means that let networking is an object that conforms to Networking. The compiler doesn't know which object it will be, or what that object's type is. All the compiler knows is that there will be an object, any object, that will be assigned to let networking when we initialize PostsDataSource, and that object conforms to Networking. We're essentially only sure that we'll have a box that contains a Networking object. To know exactly which object was put in that box, we need to open that box at runtime, peek inside, and find the object.

It's important to know that existentials are relatively expensive to use because the compiler and runtime can’t pre-determine how much memory should be allocated for the concrete object that will fill in the existential. Whenever you call a method on an existential, like the networking property in the snippet you saw earlier, the runtime will have to dynamically dispatch this call to the concrete object which is slower than a static dispatch that goes directly to a concrete type.

The Swift team has determined that it’s currently too easy to reach for an existential over a concrete object. This essentially means that a lot of us are writing code that uses protocols (existentials) that harm our performance without us really being aware of it. For example, there’s nothing wrong with the old fashioned PostsDataSource you saw earlier, right?

struct PostsDataSource {
    let networking: Networking
    // ...
}

I’m sure we all have code like this, and in fact, we might even consider this best practice because we're not depending on concrete types which makes our code easier to test and maintain.

Sadly, this code uses an existential by having a property that has Networking as its type. This means that it’s not clear for the runtime how much memory should be allocated for the object that will fill in our networking property, and any calls to fetchPosts will need to be dynamically dispatched.

By introducing the any keyword, the language forces us to think about this. In Swift 5.6 annotating our let networking: Networking with any is optional; we can do this on our own terms. However, in Swift 6 it will be required to annotate existentials with the any keyword.

Digging deeper into the any keyword

As I was reading the proposal for any, I realized that what the Swift team seems to want us to do, is to use generics and concrete types rather than existentials when possible. It’s especially this part from the introduction of the proposal that made this clear to me:

Despite these significant and often undesirable implications, existential types have a minimal spelling. Syntactically, the cost of using one is hidden, and the similar spelling to generic constraints has caused many programmers to confuse existential types with generics. In reality, the need for the dynamism they provided is relatively rare compared to the need for generics, but the language makes existential types too easy to reach for, especially by mistake. The cost of using existential types should not be hidden, and programmers should explicitly opt into these semantics.

So how should we be writing our PostsDataSource without depending on a concrete implementation directly? And how can we do that without using an existential since clearly existentials are less than ideal?

The easiest way would be to add a generic to our PostsDataSource and constraining it to Networkingas follows:

protocol Networking {
    func fetchPosts() async throws -> [Post]
    // ...
}

struct PostsDataSource<Network: Networking> {
    let networking: Network
    // ...
}

By writing our code like this, the compiler will know up front which type will be used to fill in the Network generic. This means that the runtime will know up-front how much memory needs to be allocated for this object, and calls to fetchPosts can be dispatched statically rather than dynamically.

💡Tip: If you’re not too familiar with generics, take a look at this article to learn more about generics in Swift and how they’re used.

When writing PostsDataSource as shown above, you don’t lose anything valuable. You can still inject different concrete implementations for testing, and you can still have different instances of PostsDataSource with different networking objects even within your app. The difference compared to the previous approach is that the runtime can more efficiently execute your code when it know the concrete types you’re using (through generics).

Alternatively, you could rewrite let networking to use some Networking instead of using a generic. To learn more about some, and how you can use it to replace generics in some situations, take a look at this post.

The only thing we’ve lost by not using any is the ability to dynamically swap out the networking implementation at runtime by assigning a new value of a different concrete type to networking (which we couldn’t do anyway because it’s defined as a let).

It's interesting to note that because we have to choose between any, some, and a generic, when we define our let networking, it's easier to choose the correct option. We could use : any Networking wherever we'd write : Networking in Swift 5.5 and earlier, and our code would work just fine but we might be using a suboptimal existential instead of a concrete type that can benefit from compile-time optimizations and static dispatch at runtime. In some cases, that's exactly what you want. You might need the flexibility that an existential provides, but often you'll find that you don't need an existential at all.

So how useful is the any keyword really? Should you be using it in Swift 5.6 already or is it better to just wait until the compiler starts enforcing any in Swift 6?

In my opinion, the any keyword will provide developers with an interesting tool that forces them to think about how they write code, and more specifically, how we use types in our code. Given that existentials have a detrimental effect on our code’s performance I’m happy to see that we need to explicitly annotate existentials with a keyword in Swift 6 onward. Especially because it’s often possible to use a generic instead of an existential without losing any benefits of using protocols. For that reason alone it’s already worth training yourself to start using any in Swift 5.6.

Note: take a look at my post comparing some and any to learn a bit more about how some can be used in place of a generic in certain situations.

Using any now in Swift 5.6 will smoothen your inevitable transition to Swift 6 where the following code would actually be a compiler error:

protocol Networking {
    func fetchPosts() async throws -> [Post]
    // ...
}

struct PostsDataSource {
    // This is an error in Swift 6 because Networking is an existential
    let networking: Networking
    // ...
}

The above code will at least need to be written using any Networking in Swift if you really need the existential Networking. In most cases however, this should prompt you to reconsider using the protocol in favor of a generic or writing some Networking in order to improve runtime performance.

Whether or not the performance gains from using generics over existentials is significant enough to make a difference in the average app remains to be seen. Being conscious of the cost of existentials in Swift is good though, and it’s definitely making me reconsider some of the code I have written.

The any keyword in Swift 5.7

In Swift 5.7 the any keyword is still not mandatory for all existentials but certain features aren't available to non-any protocols. For example, in Swift 5.7 the requirements around protocols with a Self requirement have been relaxed. Previously, if you wanted to use a protocol with an associated type of Self requirement as a type you would have to use some. This is why you have to write var body: some View in SwiftUI.

In Swift 5.7 this restriction is relaxed, but you have to write any to use an existential that has an associated type or Self requirement. The following example is an example of this:

protocol Content: Identifiable {
    var url: URL { get }
}

func useContent(_ content: any Content) {
    // ...
}

The code above requires us to use any Content because Content extends the Identifiable protocol which has an associated type (defined as associatedtype ID: Hashable). For that reason, we have to use any if we can't use some.

The same is true for protocols that use a primary associated type. Using an existential with a primary associated type already requires the any keyword in Swift 5.7.

Note that any isn't a drop in replacement for some as noted in my comparison of these two keywords. When using any, you'll always opt-in to using an existential rather than a concrete type (which is what some would provide).

Even though any won't be completely mandatory until Swift 6.0 it's interesting to see that Swift 5.7 already requires any for some of the new features that were made available with Swift 5.7. I think this reinforces the point that I made earlier in this post; try to start using any today so you're not surprised by compiler errors once Swift 6.0 drops.

Categories

Quick Tip Swift