eugenemind.com
← Back to blog

October 17, 2026 · 15 min read

some vs any in Swift: The Difference and When to Use Each

#swift #ios #generics #swiftui

If you’ve been developing iOS apps for a while, you probably remember when you could use a protocol as the type of a property, parameter, or return value without any extra keywords.

Swift 5.1 introduced some, followed by any in Swift 5.6. Today, instead of plain Protocol, we increasingly see some Protocol and any Protocol.

At first glance, both hide an implementation behind a protocol. So why do we need two mechanisms? Why can’t a function returning some return two different concrete types, while one returning any can? And is some really always faster?

Let’s start with an everyday checkout example, then look at public SDK APIs and what happens under the hood.

Start with a Regular Protocol

Imagine building a checkout screen where customers can pay by card or QR code.

import Foundation

protocol PaymentMethod {
    func pay(amount: Decimal)
}

struct CardPayment: PaymentMethod {
    func pay(amount: Decimal) {
        print("Card payment: \(amount)")
    }
}

struct QRPayment: PaymentMethod {
    func pay(amount: Decimal) {
        print("QR payment: \(amount)")
    }
}

Nothing unusual so far. PaymentMethod defines a contract, and CardPayment and QRPayment provide implementations. Now let’s write a checkout function:

func checkout<T: PaymentMethod>(
    using method: T,
    amount: Decimal
) {
    method.pay(amount: amount)
}

This is a generic function. Rather than naming a concrete type, we introduce T, constrained to conform to PaymentMethod.

checkout(using: CardPayment(), amount: 100)
checkout(using: QRPayment(), amount: 200)

Swift infers T as CardPayment for the first call and QRPayment for the second. Crucially, a generic function does not automatically turn its argument into a protocol-typed existential. It works with a concrete T that satisfies a constraint. That type identity is preserved even though the generic function was written without knowing the eventual type.

But what if we need to handle different implementations through one interface?

any: When the Concrete Type Can Change

Suppose the checkout screen lets the customer choose a payment method. We want to store the available options in one array:

let paymentMethods: [any PaymentMethod] = [
    CardPayment(),
    QRPayment()
]

A Swift array has one element type, while CardPayment and QRPayment are different structs. any PaymentMethod solves that problem.

any is a Swift keyword that denotes an existential type. An existential can hold a value of any concrete type conforming to the specified protocol.

The term existential comes from the mathematical symbol ∃, meaning “there exists.” We know that some conforming concrete type exists inside the value, but we do not fix that type as part of the variable’s static type.

var selectedMethod: any PaymentMethod = CardPayment()

selectedMethod.pay(amount: 100)
selectedMethod = QRPayment()
selectedMethod.pay(amount: 200)

The variable remains any PaymentMethod even though it first contains CardPayment and then QRPayment. That is useful for checkout: switching payment methods doesn’t require replacing the ViewModel or changing its generic parameters.

Why Did Swift Make any Explicit?

Before Swift 5.6, we could write:

let method: PaymentMethod = CardPayment()

That was already an existential, but the syntax looked like an ordinary protocol name.

In SE-0335, Introduce existential any, the authors put the motivation plainly:

“The cost of using existential types should not be hidden”

And the concern wasn’t limited to runtime speed. The old syntax blurred two different constructs:

func checkout<T: PaymentMethod>(_ method: T)
func checkout(_ method: any PaymentMethod)

In the first declaration, PaymentMethod constrains a generic parameter. In the second, the parameter itself is an existential. The proposal pointed out that this ambiguity encouraged developers to reach for existentials when generics were what they actually needed. Problems often appeared later when code relied on concrete type identity or on Self and associatedtype requirements.

The explicit any keyword makes type erasure visible at the API boundary. It does not mean the runtime forgets the original type. Swift still retains the information needed to manage and use the value. What gets erased is the ability of ordinary calling code to rely on that concrete type as part of the static type system.

Also, any PaymentMethod is not the same as Any. Any can contain almost any Swift value. any PaymentMethod only accepts conforming types and lets us call the protocol’s requirements without a cast.

some: Hiding an SDK’s Implementation

Now imagine we’re building a payment SDK used by other development teams.

There are two distinct sides to this example. Inside the SDK, we own the implementation and can reorganize its types. Outside the SDK, consumers see its public API.

Here, PaymentMethod is a public protocol, while CardProcessor is an internal SDK type. We write internal explicitly for clarity, even though it is Swift’s default access level.

public protocol PaymentMethod {
    func pay(amount: Decimal)
}

internal struct CardProcessor: PaymentMethod {
    func pay(amount: Decimal) {
        print("Processing card payment")
    }
}

Suppose the SDK needs a public factory for its default payment processor.

Option 1: Return the Concrete Type

The obvious approach is:

public func makePaymentMethod() -> CardProcessor {
    CardProcessor()
}

This does not compile. A public function cannot expose an internal return type. We could make CardProcessor public instead:

public struct CardProcessor: PaymentMethod {
    public func pay(amount: Decimal) {
        print("Processing card payment")
    }
}

public func makePaymentMethod() -> CardProcessor {
    CardProcessor()
}

That compiles, but now our implementation type is part of the SDK’s public contract. Consumers can depend directly on CardProcessor.

What if we later add payment logging?

internal struct LoggingPayment<Base: PaymentMethod>: PaymentMethod {
    let base: Base

    func pay(amount: Decimal) {
        print("Payment started")
        base.pay(amount: amount)
    }
}

The actual result type becomes LoggingPayment<CardProcessor>. If we expose that type in a public return signature, LoggingPayment also has to become public. Internal SDK structure is gradually leaking into its API.

Option 2: Return any PaymentMethod

We can hide the implementation behind an existential:

public func makePaymentMethod() -> any PaymentMethod {
    LoggingPayment(base: CardProcessor())
}

SDK consumers no longer need to know either implementation type. This is a perfectly valid design. The tradeoff is that the result is an existential, even though the factory always constructs one specific concrete type. We have erased the type without actually needing the result to hold different concrete types.

Option 3: Return some PaymentMethod

This is where some helps:

public func makePaymentMethod() -> some PaymentMethod {
    LoggingPayment(base: CardProcessor())
}

We still hide the internal SDK types, but the result is not an existential.

some is a Swift keyword that denotes an opaque result type when used in a return position. The implementation chooses one concrete underlying type. Callers do not know its name, but the compiler retains its type identity.

let method = makePaymentMethod()
method.pay(amount: 100)

The consumer doesn’t need to know that the underlying type is LoggingPayment<CardProcessor>.

Without some, the two main options were exposing the concrete type or returning an existential with any. some offers a third option: hide the concrete type from clients while keeping it statically determined.

One caveat matters for SDK authors: a hidden underlying type is not automatically free to change without compatibility consequences. When using library evolution, opaque result types have ABI and resilience rules that must be considered.

Why Can’t some Return Just Anything?

Consider a factory that chooses a payment method based on a condition:

func makePaymentMethod(useQR: Bool) -> some PaymentMethod {
    if useQR {
        return QRPayment()
    }

    return CardPayment()
}

This fails to compile. Both structs conform to PaymentMethod, but an opaque result declaration must have one underlying concrete type. QRPayment and CardPayment are different types.

If runtime selection is the requirement, any fits:

func makePaymentMethod(useQR: Bool) -> any PaymentMethod {
    if useQR {
        return QRPayment()
    }

    return CardPayment()
}

That is the practical distinction: some hides one definite type, while any can represent values of different conforming types.

A Simple Analogy: Toys in Boxes

Imagine a toy shop selling cars, robots, and dinosaurs. All are toys, but they are different kinds of objects.

Now picture two boxes.

The first is a surprise box (any). It might contain a toy car today, a robot tomorrow, or a dinosaur next week. All we know is that it contains a toy. That’s like any Toy: the concrete type can change.

The second contains a secret model (some). A manufacturer makes one particular toy model but doesn’t reveal its name to the buyer. Every box in that product line contains the same model type. Individual toys differ, but their type stays fixed. That’s like some Toy: the producer chooses one concrete type and hides its name.

Generics are like a box where the customer chooses the model. Order a robot box and it works with robots; choose a toy car and it works with toy cars.

The important point isn’t that the box is closed. Both some and any hide a concrete type from the API consumer. some promises one underlying type; any allows different types behind the same interface.

How This Matters in Checkout

Back to our checkout screen. Suppose customers can switch between card and QR payments. First, try storing the payment method as a generic parameter:

final class CheckoutViewModel<Method: PaymentMethod> {
    private(set) var selectedMethod: Method

    init(selectedMethod: Method) {
        self.selectedMethod = selectedMethod
    }

    func select(_ method: Method) {
        selectedMethod = method
    }
}

After creating this instance:

let viewModel = CheckoutViewModel(
    selectedMethod: CardPayment()
)

we have a CheckoutViewModel<CardPayment>.

viewModel.select(QRPayment()) // Compile-time error

Method is fixed for that instance. If switching between implementations is a feature, an existential fits better:

final class CheckoutViewModel {
    private(set) var selectedMethod: any PaymentMethod

    init(selectedMethod: any PaymentMethod) {
        self.selectedMethod = selectedMethod
    }

    func select(_ method: any PaymentMethod) {
        selectedMethod = method
    }

    func pay(amount: Decimal) {
        selectedMethod.pay(amount: amount)
    }
}

Now switching is straightforward:

let viewModel = CheckoutViewModel(
    selectedMethod: CardPayment()
)

viewModel.select(QRPayment())
viewModel.pay(amount: 500)

We chose any because it matches the screen’s requirements, not because its syntax looks convenient. some serves a different role, such as hiding the processor returned by an SDK factory.

What If We Use some in Parameters?

So far, we’ve used some in return types. Swift also allows it in function parameters:

func checkout(using method: some PaymentMethod) {
    method.pay(amount: 100)
}

Here the meaning changes. In parameter position, some is shorthand for a generic parameter. The declaration is equivalent in meaning to:

func checkout<T: PaymentMethod>(using method: T) {
    method.pay(amount: 100)
}

The caller chooses the concrete type. We can pass either CardPayment or QRPayment, and T is inferred for each call.

Why not always use the shorter form? Sometimes two parameters must share the same concrete type:

func compare<T: PaymentMethod>(
    _ first: T,
    _ second: T
) {
    // ...
}

Here both arguments must use the same T. Contrast that with:

func compare(
    _ first: some PaymentMethod,
    _ second: some PaymentMethod
) {
    // ...
}

Each some introduces an independent generic parameter. This version accepts a CardPayment and a QRPayment together. The shorter syntax is useful when no relationship between generic parameter types needs to be expressed.

What Happens Under the Hood?

Now that we know what some and any are for, let’s look at why their runtime costs can differ.

Return to this checkout value:

let method: any PaymentMethod = CardPayment()

We created a CardPayment, but stored it as any PaymentMethod. Later, we could replace it with QRPayment or another conforming implementation.

That flexibility gives Swift more work to do. The compiler cannot assume method always contains a CardPayment. The next value could have a different size, stored properties, and implementation of pay(amount:).

Swift therefore needs to store the value along with enough information to manage it. For a typical value-type existential conforming to one protocol on a 64-bit platform, the representation can be simplified as:

┌──────────────────────────────┐
│ Value buffer: 3 words        │
├──────────────────────────────┤
│ Type metadata pointer        │
├──────────────────────────────┤
│ Protocol witness table ptr   │
└──────────────────────────────┘

That is typically 40 bytes. It is not the layout of every existential: class-constrained existentials, for example, have a different representation.

The first three machine words form an inline value buffer. If a struct is small enough and eligible for inline storage, Swift can keep it directly in the container. So using any does not automatically allocate an object on the heap. Larger or otherwise unsuitable values may require indirect storage, potentially including heap allocation.

Now suppose we replace the payment method:

var method: any PaymentMethod = CardPayment()
method = QRPayment()

How does Swift know what is inside? Type metadata describes the concrete type stored in the container.

But knowing the type is not enough. When we call:

method.pay(amount: 100)

Swift must find the appropriate implementation. That’s where the Protocol Witness Table (PWT) comes in. A protocol conformance supplies the information needed to map protocol requirements to a concrete type’s implementations. CardPayment and QRPayment have their own conformances, so a call through the existential can reach the correct method.

Now consider copying an existential instead of calling a protocol method. Swift must know how to copy and destroy the underlying value, particularly when it manages resources. Those operations are described by the Value Witness Table (VWT), accessed through the type’s metadata.

In short, the PWT supports behavior through protocol requirements, while the VWT supports operations on values themselves. They serve different purposes, and the VWT is not an extra mandatory field in the container diagram.

This helps explain where existential overhead can come from: a container must support different concrete types, and protocol calls generally involve indirection.

Does that mean generics and some always call methods directly? No.

Consider our generic function again:

func checkout<T: PaymentMethod>(
    using method: T,
    amount: Decimal
) {
    method.pay(amount: amount)
}

The function is written without choosing CardPayment or QRPayment. Swift may compile a shared generic implementation that receives type and conformance information. In that implementation, pay(amount:) may also be dispatched through a witness table. Generics do not guarantee static dispatch by themselves.

But the compiler has another option. At a call site such as:

checkout(using: CardPayment(), amount: 100)

it may know that T is CardPayment and specialize the function for that type. This can enable a direct call and potentially inlining.

An opaque result from some also has one definite underlying type, even though callers cannot name it. That preserves opportunities for optimization. With any, the stored type can vary, so proving which implementation will be called is generally harder. Even then, an optimizer may sometimes see through an existential and remove some overhead.

The useful conclusion is not that some is fast and any is slow. Generics and opaque types retain more static type information, which can give the compiler more opportunities to specialize code.

For a real checkout, a network request will dwarf the cost of an indirect method call. In a hot loop executing millions of times, the difference may matter. That’s where profiling and inspecting compiler optimizations become worthwhile. API requirements should guide the choice first; performance claims should be measured.

Why Is some View Everywhere in SwiftUI?

The best-known use of some is probably SwiftUI’s body property:

struct CheckoutView: View {
    var body: some View {
        VStack {
            Text("Checkout")

            Button("Pay") {
                // ...
            }
        }
    }
}

It might look as though body simply returns anything conforming to View. But SwiftUI builds interfaces out of concrete types. Text is a struct; Button is a generic struct; VStack stores its content through a generic parameter. Modifiers can add still more wrapper types.

Even a small screen can therefore have an unwieldy concrete type. Writing that type by hand would be unpleasant and fragile. some View hides the type’s spelling while preserving its identity for the compiler. This helps with type checking and optimization.

Why Not Just Write any View?

Try replacing some with any:

struct CheckoutView: View {
    var body: any View {
        Text("Checkout")
    }
}

This does not satisfy View’s requirements. It seems odd: Text conforms to View, and any View can contain a Text.

The distinction is that the existential type any View does not itself generally conform to View. These are different claims:

  • A value inside any View has a concrete type conforming to View.
  • The existential type any View itself conforms to View.

The first is true. The second is not true here.

View has an associated type named Body. A conforming type must have a definite Body. An any View can hold views with different Body types, so it cannot simply provide one concrete associated type covering all possibilities. That’s why it cannot replace some View in body.

Then What Is AnyView For?

SwiftUI provides a separate struct named AnyView. It performs type erasure, but unlike any View, AnyView itself conforms to View.

func makePaymentScreen(useQR: Bool) -> AnyView {
    if useQR {
        return AnyView(Text("Scan QR code"))
    }

    return AnyView(Button("Pay by card") {
        // ...
    })
}

The branches contain different views, but the function always returns one concrete type: AnyView.

In modern SwiftUI, however, we often don’t need AnyView. A conditional inside @ViewBuilder works naturally:

struct PaymentScreen: View {
    let useQR: Bool

    var body: some View {
        if useQR {
            Text("Scan QR code")
        } else {
            Button("Pay by card") {
                // ...
            }
        }
    }
}

Why does this compile when our earlier some PaymentMethod function with different return types did not? body uses @ViewBuilder. The result builder transforms the branches into a composite value with one concrete underlying type, using types such as _ConditionalContent.

AnyView, by contrast, actually erases the view type. Apple notes that when the type of the view inside an AnyView changes, the old hierarchy is destroyed and a new one is created. There is no reason to wrap an entire view tree in AnyView without a need for that erasure.

The three names now describe distinct things: some View hides a definite, potentially complex view type; any View is an existential that cannot directly serve as body’s conforming return type; and AnyView is a concrete SwiftUI type that performs erasure while itself conforming to View.

Sources