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.0Remember
some P= one fixed hidden type, statically dispatchedany P= a box that can hold different types, dynamic dispatch- Prefer
some; useanyfor heterogeneous storage
Watch out for
- Returning different concrete types from a
-> some Pfunction and wondering why it doesn't compile.