Existential any in Swift, and when it becomes required

⋅ 6 min read ⋅ Swift Swift Evolution Protocol

Table of Contents

These two functions look like small variations on each other.

protocol Shape {
func area() -> Double
}

func draw<S: Shape>(_ shape: S) { }
func draw(_ shape: Shape) { }

They are not variations. They are two different language features that happen to share a spelling.

The first is a generic. The compiler knows the concrete type at each call site, and can inline and specialize the call.

The second takes an existential: a box that can hold any value conforming to Shape, with the concrete type erased at compile time. That box is not free, and nothing in func draw(_ shape: Shape) says so.

SE-0335, introduced in Swift 5.6, gives the second one a name.

func draw(_ shape: any Shape) { }

The proposal calls the old spelling actively harmful. It made existentials the easiest thing to reach for by accident, and the accident tends to surface late, when the code has to be rewritten as a generic.

What the bare name was hiding

Two things. One is speed. The other is what we can still do with the value once it is in the box.

The box is slower. When the compiler knows the concrete type, as it does with a generic, it can build a copy of the function for that exact type and call it directly. This is called specialization, and it is why a generic can be as fast as code written for one type by hand.

An existential never gets that. The type inside the box is not known until the program runs, and it can change from one call to the next. So the value usually lives on the heap, unless it is small enough to fit in a three-word slot inside the box. Every use pays for reference counting, an extra pointer hop, and method calls that are looked up at runtime instead of inlined.

The box forgets part of the type. Erasing the concrete type also erases everything that depended on it. The clearest case is a protocol with an associated type.

protocol P {
associatedtype A
func test(a: A)
}

func generic<ConcreteP: P>(p: ConcreteP, value: ConcreteP.A) {
p.test(a: value)
}

func useExistential(p: any P) {
generic(p: p, value: ???) // what type would P.A even be?
}

There is nothing we can write in place of ???. Inside generic, the compiler knows what ConcreteP.A is. Inside useExistential, it has thrown that away, so there is no A left to name.

Code like this cannot be patched. It has to be rewritten as a generic, and by the time the problem shows up there is often a lot of it.

That is the case for the keyword. Putting a value in the box should be something we choose, not something we get by typing the shortest thing that compiles.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Where the migration actually stands

The plan has always been that any becomes required in a future language mode. It is worth being precise about where that has got to, because the common assumption is wrong.

Swift 6 language mode does not require any. This compiles clean under -swift-version 6, with no error and no warning:

protocol Shape {}
struct Circle: Shape {}

let a: Shape = Circle()
func f(_ x: Shape) {}

So upgrading to the Swift 6 language mode does not start this migration. The Language Steering Group left it out of Swift 6 on purpose, and when someone asked again in May 2026 the answer was the same: a future language mode, with no version and no date.

One case already warns today, with no flag at all. Protocols with an associated type or a Self requirement warn on sight:

protocol Container { associatedtype Item }

let c: Container
// warning: use of protocol 'Container' as a type must be written 'any Container';
// this will be an error in a future Swift language mode [#ExistentialAny]

SE-0335 asked for this on purpose. SE-0309 had just made these protocols usable as existentials for the first time, and the authors did not want people writing a pile of brand-new code that would become invalid later. So that corner of the language jumped the queue.

Any and AnyObject are not affected. They keep their spelling, and the flag does not warn on them.

Turning it on early

The migration is available behind an upcoming feature flag, ExistentialAny, which shipped in Swift 5.8.

-enable-upcoming-feature ExistentialAny

Every bare protocol name in type position then warns, with a fix-it that writes the any for you.

let a: Shape = Circle()
// warning: use of protocol 'Shape' as a type must be written 'any Shape';
// this will be an error in a future Swift language mode [#ExistentialAny]

Look at the [#ExistentialAny] at the end of the warning. That is the name of the group this warning belongs to. Swift can turn a whole group of warnings into errors with -Werror and the group name:

-enable-upcoming-feature ExistentialAny -Werror ExistentialAny

Now a missing any is a build error instead of a warning. That is the setting to use once a module has no warnings left, because it stops new ones from creeping back in.

In Swift 6.4, the @diagnose attribute lets one function or type turn this group back into a warning, or silence it, while the rest of the module keeps the error. So a single file that cannot be fixed yet does not force us to turn the error off everywhere.

Adding any is not always the fix

The fix-it is mechanical, which makes it tempting to accept every one of them and move on. That is worth resisting on the first pass, because a large share of existentials in ordinary code were never meant to be existentials.

If a function takes one value and only needs to call protocol methods on it, it does not need a box:

// The fix-it will suggest this
func draw(_ shape: any Shape) { }

// But this is usually what was meant
func draw(_ shape: some Shape) { }

some Shape is an opaque type. The caller's concrete type is preserved, the call can specialize, and none of the existential costs apply. Since Swift 5.7 it works in parameter position, so the change is this small.

Keep any where the dynamism is the point. A single array holding circles and squares together, a stored property whose concrete type changes at runtime, a heterogeneous collection of delegates: those need type erasure, and any is how you say so.

The useful way to read each warning is as a question. Does this need to hold different types at different times? If yes, write any. If no, write some.

The parentheses tax

There is one more reason not to rush the whole codebase through the fix-it today, and it is small but it touches every optional protocol type you own.

An optional existential currently needs parentheses, because ? binds tighter than any. So the fix-it wraps the type:

let x: P? = S()
// ^
// (any P)

That is the literal replacement the compiler offers today. Accept it across a codebase and every delegate, data source, and optional protocol property picks up a pair of parentheses:

weak var delegate: (any ScannerDelegate)?
var dataSource: (any FeedDataSource)?

Swift 6.4 changes that. SE-0521 lets a trailing ? cover the whole existential, so any P? is legal and means what it looks like. The fix-it changes with it: for a single protocol that is optional or implicitly unwrapped, it now writes any P instead of (any P).

weak var delegate: any ScannerDelegate?
var dataSource: any FeedDataSource?

Same type, same mangled name, fewer parentheses. Migrating a large codebase on Swift 6.4 or later produces the spelling you would have written by hand.

You can easily support sarunw.com by checking out this sponsor.

Sponsor sarunw.com and reach thousands of iOS developers.

Summary

any marks a value kept in a type-erased box, which is slower than a generic and cannot use the protocol's associated types. Swift does not require it yet, not even in Swift 6 mode, and there is no date for when it will. Turn on -enable-upcoming-feature ExistentialAny to see where you use existentials, and treat each warning as a question: write some if the code only ever handles one type, and any if it really holds different types.


Read more article about Swift, Swift Evolution, Protocol, or see all available topic

Enjoy the read?

If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.

Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.

If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.

Become a patron Buy me a coffee Tweet Share
Previous
Why some Swift types only appear when you import two modules

Map, PhotosPicker, and WebView are not in SwiftUI. They live in cross-import overlays, modules that only show up when you import both halves. Here is what that means and how to spot one.

← Home