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. If you see this in your code, you might think it’s similar to the some keyword but it’s not quite the same thing.

Let’s take a look at the any keyword as it’s intended to be used:

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.

While some allows us to write code with generics that more or less ignores, or discards, a protocol’s associated types while expecting that every return value 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. For the sake of brevity, I won’t cover what an existential is in this post; that’s a topic for a different post. The bottom line is that an existential is like a "box type". You know something is, for example, a Pizza (where Pizza is defined as a protocol) but you have no idea what kind of pizza is in the box. You have to open the box to figure out which kind of pizza the box contains.

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 the 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 following code, right?

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

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

I’m sure we all have code like this, and in fact, we might even consider this best practice.

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.

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 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).

The only thing we’ve lost is the ability to dynamically swap out the networking implementation 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).

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 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 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