some vs any: opaque and boxed types

Swift · Advanced · 12 min

SwiftUI's some View and modern APIs depend on this distinction, and it affects both performance and what compiles.

Two ways to talk about "a value of some type conforming to P":

Opaque types — some P — mean one specific concrete type, chosen by the implementation and hidden from the caller. In a return position, func makeShape() -> some Shape must return the same concrete type on every path. The compiler knows the real type, so calls are statically dispatched and can be specialized. SwiftUI's var body: some View relies on this to keep huge nested view types hidden but fully typed. In a parameter, some P is just a generic parameter.

Existential types — any P — are a box that can hold a value of any conforming type, and the type can differ from value to value. You need them for heterogeneous collections, [any Shape], or a stored property whose concrete type changes at run time. The cost: values may be heap-allocated if they don't fit inline, calls go through a witness table, and the optimizer can't specialize.

Limits of any P: when a protocol has Self or associated-type requirements, some members can't be used on the box directly (for example, == from Equatable between two any Equatable). Swift opens an existential when you pass it to a generic parameter, so func check(_ x: some Equatable) works with an any Equatable argument.

Rule of thumb: write some by default; use any when you truly need values of different types in the same place.

Example

protocol Shape { func area() -> Double }
struct Square: Shape { var s: Double; func area() -> Double { s * s } }
struct Circle: Shape { var r: Double; func area() -> Double { 3.14 * r * r } }

func unitShape() -> some Shape {      // always the same hidden type
    Square(s: 1)
    // returning Circle on another path would not compile
}

func describe(_ shape: some Shape) -> String {  // generic, specialized
    "area \(shape.area())"
}

var mixed: [any Shape] = [Square(s: 2), Circle(r: 1)]  // boxes
mixed.append(unitShape())

for s in mixed {
    print(describe(s))                // existential opened for the generic call
}
// area 4.0 / area 3.14 / area 1.0

Remember

  • some P = one fixed hidden type, statically dispatched
  • any P = a box that can hold different types, dynamic dispatch
  • Prefer some; use any for heterogeneous storage

Watch out for

  • Returning different concrete types from a -> some P function and wondering why it doesn't compile.

Check yourself

  1. You need an array holding both Square and Circle values. Which element type works?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.swift.org/swift-book/documentation/the-swift-programming-language/opaquetypes/

Builds on

Unlocks

Nothing builds on this yet.