Skip to content
Lesson 4 of 7

Step 1 of 5 · Reading · ~3 min

Learn

Builders and Opaque Types

some declares an opaque return type: "this returns one specific type that satisfies this protocol, and I am not telling you which one."

protocol Shape {
    func area() -> Double
}

struct Circle: Shape {
    let radius: Double
    func area() -> Double { return 3.14159 * radius * radius }
}

func makeShape() -> some Shape {
    return Circle(radius: 5)
}

print(makeShape().area())      // 78.53975

The caller gets the Shape interface and nothing more. The compiler, though, still knows it is a Circle, and that asymmetry is the whole point.

Opaque is not the same as hidden-at-runtime

Returning a bare protocol type instead boxes the value in an existential container: the concrete type is erased, calls dispatch dynamically through a witness table, and the compiler can neither inline nor specialize. some Shape keeps the identity at compile time, so dispatch stays static and the value keeps its size and layout.

The consequence learners hit first is type identity. Two calls to a function returning some Shape are guaranteed to return the same type, so the results are comparable and can be put in the same array. Two calls returning a boxed protocol carry no such guarantee.

The one-concrete-type rule

A function returning some Protocol must return exactly one concrete type on every path:

func pick(_ big: Bool) -> some Shape {
    if big { return Circle(radius: 9) }
    return Square(side: 1)      // error: branches return different types
}

If you genuinely need to choose the type at runtime, you need an existential instead. On a current toolchain that is spelled any Shape; on this course's grader (Swift 5.2) the any spelling does not exist yetany is not a keyword there and the line fails with "use of undeclared type 'any'". The 5.2 way to write the same thing is the bare protocol name, -> Shape.

Why SwiftUI needs it

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Hello")
            Button("Tap") { }
        }
    }
}

The real type of that body is a deeply nested generic — VStack<TupleView<(Text, Button<Text>)>> — that no one wants to write and that changes whenever you edit the body. some View lets the compiler keep the exact type for specialization while you write three words.

Choosing between them

some Protocolexistential (Shape here, any Shape on 5.6+)
Concrete typefixed, one per functionany conformer, chosen at runtime
Dispatchstatic, specializabledynamic through a witness table
Heterogeneous arraynoyes

Reach for some by default and fall back to an existential only when you actually need to mix types in one collection or decide the type at runtime.

Your exercise

You will return a Square from a function typed -> some Shape and print its area to two decimals. String(format:) is a Foundation API, not a stdlib one, so the file must start with import Foundation — without it the build fails with "incorrect argument labels in call (have 'format:_:', expected 'repeating:count:')", which is the grader's confusing way of saying the Foundation overload was never visible.

Up nextGenerics with ConstraintsGenerics, Concurrency, and Memory

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…