Bird
Raised Fist0
Swiftprogramming~15 mins

Opaque types with some keyword in Swift - Deep Dive

Choose your learning style10 modes available
Overview - Opaque types with some keyword
What is it?
Opaque types in Swift let you hide the exact type a function or property returns while still guaranteeing it conforms to a specific protocol or type. The 'some' keyword is used to declare these opaque return types. This means you promise to return a type that meets certain requirements, but you don't reveal the exact type to the caller. It helps keep your code flexible and hides implementation details.
Why it matters
Without opaque types, you often have to expose concrete types or use less safe options like type erasure, which can make code harder to understand and maintain. Opaque types let you write cleaner APIs that hide complexity and allow you to change internal details without breaking code that uses your functions. This leads to safer, more modular, and easier-to-evolve code.
Where it fits
Before learning opaque types, you should understand Swift protocols, generics, and basic type system concepts. After mastering opaque types, you can explore advanced generics, type erasure, and protocol-oriented programming to write highly reusable and flexible Swift code.
Mental Model
Core Idea
Opaque types let you say 'I will return some type that meets this protocol' without saying exactly which type.
Think of it like...
It's like ordering a coffee that will be served in a cup, but the café doesn't tell you if it's ceramic, glass, or paper — you just know it will be a cup you can drink from.
Function returns ──▶ some Protocol
  └─ hides exact type
  └─ guarantees protocol conformance
Build-Up - 7 Steps
1
FoundationUnderstanding Protocols in Swift
🤔
Concept: Protocols define a blueprint of methods and properties that types can adopt.
In Swift, a protocol is like a contract. For example, a protocol 'Shape' might require a property 'area'. Any type that says it conforms to 'Shape' must provide that property. Example: protocol Shape { var area: Double { get } } struct Circle: Shape { var radius: Double var area: Double { return .pi * radius * radius } }
Result
You can use 'Shape' as a type to refer to any shape, but you don't know the exact kind of shape.
Understanding protocols is key because opaque types rely on promising conformance to protocols without revealing concrete types.
2
FoundationWhat is the 'some' Keyword?
🤔
Concept: 'some' declares an opaque return type, hiding the concrete type but guaranteeing protocol conformance.
Normally, functions declare exact return types: func makeCircle() -> Circle { ... } With 'some', you can write: func makeShape() -> some Shape { return Circle(radius: 5) } Here, the caller knows the return conforms to 'Shape' but not that it's a 'Circle'.
Result
The function hides the exact type but guarantees it behaves like a 'Shape'.
The 'some' keyword lets you hide implementation details while keeping type safety.
3
IntermediateDifference Between Opaque Types and Protocol Types
🤔Before reading on: do you think 'some Shape' and 'Shape' as return types behave the same? Commit to your answer.
Concept: Opaque types keep the concrete type hidden but fixed, while protocol types erase the type and allow different types at runtime.
Returning 'some Shape' means the function returns one specific type conforming to 'Shape', but the caller doesn't know which. Returning 'Shape' (a protocol type) means the function can return any type conforming to 'Shape', possibly different each time. Example: func opaque() -> some Shape { Circle(radius: 5) } func existential() -> Shape { Bool.random() ? Circle(radius: 5) : Square(side: 3) }
Result
'some Shape' is more efficient and type-safe because the type is fixed but hidden; 'Shape' is flexible but less efficient.
Knowing this difference helps choose the right abstraction for performance and safety.
4
IntermediateOpaque Types with Properties and Methods
🤔Before reading on: do you think you can use properties and methods of the protocol on an opaque type? Commit to your answer.
Concept: You can use all protocol requirements on opaque types, but not members specific to the hidden concrete type.
Given: func makeShape() -> some Shape { Circle(radius: 5) } You can call: let shape = makeShape() print(shape.area) // OK But you cannot do: print(shape.radius) // Error: 'radius' not in 'Shape' protocol
Result
You get full access to protocol features but not to concrete type details.
This enforces encapsulation and abstraction, keeping code flexible and safe.
5
IntermediateOpaque Types in Collections
🤔
Concept: You can use opaque types to return collections of elements conforming to a protocol without exposing the collection's concrete type.
Example: func makeShapes() -> some Collection where Element == some Shape { return [Circle(radius: 3), Circle(radius: 4)] } This hides the exact collection type but guarantees it contains shapes.
Result
You get a collection of shapes without knowing if it's an array, set, or something else.
Opaque types help hide complex types in collections, improving API clarity.
6
AdvancedLimitations: Single Concrete Type per Opaque Return
🤔Before reading on: can a function returning 'some Shape' return different concrete types on different calls? Commit to your answer.
Concept: Functions with opaque return types must always return the same concrete type, even if hidden.
This code is invalid: func makeShape() -> some Shape { if Bool.random() { return Circle(radius: 5) } else { return Square(side: 3) } } Compiler error: return types differ. You must pick one concrete type to return.
Result
Opaque types enforce a fixed concrete type, ensuring type safety.
Understanding this prevents common errors and clarifies design choices.
7
ExpertOpaque Types and ABI Stability
🤔Before reading on: do you think changing the concrete type behind an opaque return breaks binary compatibility? Commit to your answer.
Concept: Opaque types help maintain ABI stability by hiding concrete types, allowing internal changes without breaking external code.
Because callers only know the protocol conformance, you can change the concrete type returned by a function without recompiling clients, as long as the new type conforms to the same protocol. This is crucial for library evolution and binary frameworks.
Result
Opaque types enable safer library updates and reduce compatibility issues.
Knowing this explains why Swift introduced opaque types for better modularity and evolution.
Under the Hood
At compile time, the Swift compiler knows the exact concrete type behind an opaque return but hides it from the caller. The caller treats the value as the protocol type but with static knowledge that the type is fixed. This allows the compiler to optimize calls and enforce type safety without runtime overhead of dynamic dispatch or type erasure.
Why designed this way?
Opaque types were introduced to balance abstraction and performance. Before, developers used protocol types (existentials) which incur runtime costs and lose some type information. Opaque types keep static type information hidden but intact, enabling better optimizations and safer APIs. This design also supports library evolution by hiding implementation details.
Caller Code
  │
  ▼
Function returns ──▶ Opaque Type (some Protocol)
  │ hides concrete type
  │ known at compile time
  ▼
Concrete Type (e.g., Circle)

Caller uses protocol interface only
Compiler enforces fixed concrete type
Optimizations possible due to static type
Myth Busters - 4 Common Misconceptions
Quick: Does 'some Shape' mean the function can return different shapes each time? Commit to yes or no.
Common Belief:Some think 'some Shape' means any shape can be returned dynamically each call.
Tap to reveal reality
Reality:'some Shape' means the function returns one fixed concrete type, hidden from the caller.
Why it matters:Misunderstanding this leads to compiler errors and design confusion.
Quick: Can you cast an opaque type back to its concrete type? Commit to yes or no.
Common Belief:People often believe they can cast opaque types to their hidden concrete types.
Tap to reveal reality
Reality:Opaque types hide the concrete type, so casting back is not possible outside the defining module.
Why it matters:Trying to cast breaks encapsulation and causes runtime errors.
Quick: Is 'some' just a synonym for 'any' in Swift? Commit to yes or no.
Common Belief:Some think 'some' and 'any' are interchangeable keywords for protocols.
Tap to reveal reality
Reality:'some' declares an opaque type with a fixed hidden concrete type; 'any' is a protocol existential allowing any conforming type dynamically.
Why it matters:Confusing these leads to wrong API design and unexpected behavior.
Quick: Does using 'some' always improve performance? Commit to yes or no.
Common Belief:Many believe opaque types always make code faster than protocol types.
Tap to reveal reality
Reality:Opaque types can improve performance by enabling static dispatch, but not always; it depends on usage and compiler optimizations.
Why it matters:Assuming performance gains without profiling can mislead optimization efforts.
Expert Zone
1
Opaque types can be combined with generics to create highly flexible yet type-safe APIs that hide complexity from users.
2
The compiler enforces that all return paths in a function with an opaque return type must return the same concrete type, even if hidden, preventing subtle bugs.
3
Opaque types improve binary stability by allowing library authors to change internal types without breaking client code, a key benefit for framework evolution.
When NOT to use
Avoid opaque types when you need to return multiple different concrete types dynamically or when the caller must know the exact type. In such cases, use protocol existentials ('any') or enums with associated types instead.
Production Patterns
In production, opaque types are used to design clean APIs that hide implementation details, such as SwiftUI's 'some View' return types. They enable library authors to evolve code internally without breaking clients and improve compile-time optimizations by preserving static type information.
Connections
Type Erasure
Opaque types provide an alternative to type erasure by hiding concrete types at compile time rather than erasing them at runtime.
Understanding opaque types clarifies when to use type erasure and when to prefer opaque returns for better performance and safety.
Encapsulation in Object-Oriented Programming
Opaque types enforce encapsulation by hiding implementation details, similar to private members in classes.
Recognizing this connection helps appreciate opaque types as a tool for abstraction and modular design.
Black Box Testing
Opaque types create a 'black box' interface where users know what a type can do but not how it does it.
This connection shows how opaque types support testing and maintenance by limiting dependencies on internal details.
Common Pitfalls
#1Trying to return different concrete types from a function with an opaque return type.
Wrong approach:func makeShape() -> some Shape { if Bool.random() { return Circle(radius: 5) } else { return Square(side: 3) } }
Correct approach:func makeShape() -> some Shape { return Circle(radius: 5) }
Root cause:Misunderstanding that opaque types require a single fixed concrete type for all return paths.
#2Attempting to access concrete type properties not declared in the protocol on an opaque type value.
Wrong approach:let shape = makeShape() print(shape.radius) // Error: 'radius' not in protocol
Correct approach:let shape = makeShape() print(shape.area) // OK: 'area' is in protocol
Root cause:Confusing opaque type's protocol interface with the hidden concrete type's full interface.
#3Confusing 'some' with 'any' and expecting dynamic dispatch behavior from opaque types.
Wrong approach:func foo() -> some Shape { ... } // expecting to switch concrete types at runtime
Correct approach:func foo() -> any Shape { ... } // allows different types dynamically
Root cause:Not understanding the semantic difference between opaque types and protocol existentials.
Key Takeaways
Opaque types with the 'some' keyword let you hide the exact return type while guaranteeing protocol conformance.
They provide a balance between abstraction and performance by preserving static type information but hiding implementation details.
Opaque types require a single concrete type per function, preventing dynamic type switching in opaque returns.
They improve API design by enforcing encapsulation and enabling safer, more flexible code evolution.
Understanding opaque types clarifies the difference between static abstraction and dynamic polymorphism in Swift.

Practice

(1/5)
1. What does the some keyword do when used in a Swift function's return type?
easy
A. It forces the function to return a specific concrete type.
B. It hides the exact return type but guarantees it conforms to a protocol.
C. It makes the function return any type without restrictions.
D. It allows the function to return multiple different types at runtime.

Solution

  1. Step 1: Understand the purpose of some keyword

    The some keyword in Swift is used to declare an opaque return type, which means the exact type is hidden but it must conform to a specified protocol.
  2. Step 2: Compare with other options

    The other options describe behaviors that do not match the opaque type concept: forcing a concrete type, returning any type freely, or allowing multiple types at runtime.
  3. Final Answer:

    It hides the exact return type but guarantees it conforms to a protocol. -> Option B
  4. Quick Check:

    Opaque types = hidden concrete type with protocol conformance [OK]
Hint: Remember: some hides type but keeps protocol rules [OK]
Common Mistakes:
  • Thinking some allows any return type without constraints
  • Confusing some with generics
  • Believing some returns multiple types dynamically
2. Which of the following is the correct syntax to declare a function returning an opaque type conforming to View protocol?
easy
A. func makeView() -> some View { Text("Hello") }
B. func makeView() -> any View { Text("Hello") }
C. func makeView() -> View { Text("Hello") }
D. func makeView() -> some View() { Text("Hello") }

Solution

  1. Step 1: Identify correct syntax for opaque return type

    The correct syntax uses some followed by the protocol name without parentheses: -> some View.
  2. Step 2: Check each option

    func makeView() -> some View { Text("Hello") } matches the correct syntax. func makeView() -> any View { Text("Hello") } uses any which is for existential types, not opaque. func makeView() -> View { Text("Hello") } returns the protocol type directly, which is not allowed. func makeView() -> some View() { Text("Hello") } incorrectly adds parentheses after View.
  3. Final Answer:

    func makeView() -> some View { Text("Hello") } -> Option A
  4. Quick Check:

    Opaque type syntax = -> some Protocol [OK]
Hint: Use some without parentheses for opaque types [OK]
Common Mistakes:
  • Using any instead of some for opaque types
  • Adding parentheses after protocol name
  • Returning protocol type directly instead of opaque type
3. What will be the output of this Swift code?
protocol Shape {
    func area() -> Double
}

struct Circle: Shape {
    var radius: Double
    func area() -> Double { 3.14 * radius * radius }
}

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

print(makeShape().area())
medium
A. 0.0
B. 4.0
C. 12.56
D. Error: Cannot use 'some' here

Solution

  1. Step 1: Understand the code flow

    The function makeShape() returns an opaque type conforming to Shape, specifically a Circle with radius 2.
  2. Step 2: Calculate the area

    The area of the circle is calculated as 3.14 * 2 * 2 = 12.56.
  3. Final Answer:

    12.56 -> Option C
  4. Quick Check:

    Circle area = π * r² = 12.56 [OK]
Hint: Calculate area using radius 2 and π=3.14 [OK]
Common Mistakes:
  • Confusing some with errors in return type
  • Using wrong formula for area
  • Assuming runtime error due to opaque type
4. Identify the error in this Swift code using opaque types:
protocol Animal {
    func sound() -> String
}

struct Dog: Animal {
    func sound() -> String { "Bark" }
}

struct Cat: Animal {
    func sound() -> String { "Meow" }
}

func makeAnimal(_ type: String) -> some Animal {
    if type == "dog" {
        return Dog()
    } else {
        return Cat()
    }
}
medium
A. Function returns different concrete types violating opaque type rules.
B. Missing protocol conformance in structs.
C. Incorrect function return type syntax.
D. No error; code is correct.

Solution

  1. Step 1: Understand opaque type restrictions

    Functions returning some Protocol must always return the same concrete type, even if hidden.
  2. Step 2: Analyze the function's return paths

    This function returns Dog or Cat depending on input, which breaks the opaque type rule.
  3. Final Answer:

    Function returns different concrete types violating opaque type rules. -> Option A
  4. Quick Check:

    Opaque type = single hidden type only [OK]
Hint: Opaque return must be one concrete type, not multiple [OK]
Common Mistakes:
  • Returning different types for opaque return
  • Confusing opaque types with protocol types
  • Ignoring compiler error about inconsistent return types
5. You want to write a function that returns an opaque type conforming to Collection protocol, but the collection should be filtered to only even numbers from an input array. Which of these implementations correctly uses some keyword and returns the filtered collection?
hard
A. func evenNumbers(from numbers: [Int]) -> some Collection { return numbers.sorted() }
B. func evenNumbers(from numbers: [Int]) -> some Collection { return numbers.map { $0 * 2 } }
C. func evenNumbers(from numbers: [Int]) -> [Int] { numbers.filter { $0 % 2 == 0 } }
D. func evenNumbers(from numbers: [Int]) -> some Collection { numbers.filter { $0 % 2 == 0 } }

Solution

  1. Step 1: Understand the requirement

    The function must return an opaque type conforming to Collection that contains only even numbers filtered from the input.
  2. Step 2: Evaluate each option

    func evenNumbers(from numbers: [Int]) -> some Collection { numbers.filter { $0 % 2 == 0 } } returns the filtered array using filter, which is a Collection and matches the requirement with some Collection. func evenNumbers(from numbers: [Int]) -> some Collection { return numbers.map { $0 * 2 } } returns doubled numbers, not filtered evens. func evenNumbers(from numbers: [Int]) -> [Int] { numbers.filter { $0 % 2 == 0 } } returns a concrete array type, not opaque. func evenNumbers(from numbers: [Int]) -> some Collection { return numbers.sorted() } returns sorted numbers but not filtered evens.
  3. Final Answer:

    func evenNumbers(from numbers: [Int]) -> some Collection { numbers.filter { $0 % 2 == 0 } } -> Option D
  4. Quick Check:

    Filter evens + opaque return = func evenNumbers(from numbers: [Int]) -> some Collection { numbers.filter { $0 % 2 == 0 } } [OK]
Hint: Use filter and some Collection for opaque filtered result [OK]
Common Mistakes:
  • Returning wrong filtered or mapped collection
  • Returning concrete type instead of opaque
  • Not filtering for even numbers
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard