Lesson 97Advanced Topics
Opaque Types & some
Hiding Implementation Details
Opaque types let you hide concrete types while maintaining type safety. The some keyword ensures the same underlying type is always used.
some vs any
some (Opaque)
Same concrete type always
Compiler knows the type
any (Existential)
Different types possible
Type erased at runtime
Syntax
func makeShape() -> some Shape {
return Circle()
}
return Circle()
}
SwiftUI Connection
SwiftUI views use some View because View has associated types. This hides complex nested view types while keeping the API clean.
var body: some View { ... }
When to Use
- some - SwiftUI views, hidden implementation
- any - Heterogeneous collections, flexible returns
- Concrete - When caller needs exact type
main.swift
// OPAQUE TYPES & SOME
// Hide concrete types while keeping type safety
// ===== THE PROBLEM =====
// Protocols with associated types can't be used as return types
protocol Animal {
associatedtype Food
func eat(_ food: Food)
}
struct Cat: Animal {
func eat(_ food: String) { print("Cat eats \(food)") }
}
struct Dog: Animal {
func eat(_ food: String) { print("Dog eats \(food)") }
}
// ERROR: Protocol with associated type can't be return type
// func makeAnimal() -> Animal { return Cat() }
// ===== SOLUTION: OPAQUE TYPES (some) =====
func makeCat() -> some Animal {
return Cat() // Concrete type hidden, but consistent
}
let pet = makeCat()
pet.eat("fish") // Works!
// ===== KEY DIFFERENCE: some vs any =====
// 'some' - Same concrete type always returned
func alwaysCat() -> some Animal {
return Cat() // Always returns Cat
}
// 'any' - Different types can be returned (type erasure)
func randomAnimal() -> any Animal {
if Bool.random() {
return Cat()
} else {
return Dog()
}
}
// ===== OPAQUE IN PROTOCOLS =====
protocol Shape {
func draw() -> String
}
struct Circle: Shape {
func draw() -> String { "○" }
}
struct Square: Shape {
func draw() -> String { "□" }
}
// Return opaque type
func makeShape() -> some Shape {
return Circle()
}
// ===== SWIFTUI EXAMPLE =====
// SwiftUI uses 'some View' extensively
// import SwiftUI
//
// struct ContentView: View {
// var body: some View {
// VStack {
// Text("Hello")
// Button("Tap") { }
// }
// }
// }
// ===== OPAQUE WITH GENERICS =====
func doubled<T: Numeric>(_ value: T) -> some Numeric {
return value * 2
}
let result = doubled(5) // Returns some Numeric
// ===== OPAQUE IN PROPERTIES =====
struct ShapeContainer {
var shape: some Shape {
Circle()
}
}
// ===== COMPARING APPROACHES =====
// 1. Concrete type - caller knows exact type
func makeCircle() -> Circle {
return Circle()
}
// 2. Protocol type (any) - type erased, flexible
func makeAnyShape() -> any Shape {
return Circle()
}
// 3. Opaque type (some) - hidden but consistent
func makeSomeShape() -> some Shape {
return Circle()
}
// ===== WHEN TO USE EACH =====
// Use 'some' when:
// - Implementation detail should be hidden
// - Same type always returned
// - Need protocol with associated types as return
// - SwiftUI views
// Use 'any' when:
// - Different types may be returned
// - Need heterogeneous collections
// - Type erasure is acceptable
// Use concrete type when:
// - Caller needs to know exact type
// - No abstraction needed
// ===== OPAQUE PARAMETERS (Swift 5.7+) =====
func feed(_ animal: some Animal) {
// Works with any Animal conforming type
}
feed(Cat())
feed(Dog())
// ===== PRIMARY ASSOCIATED TYPES =====
// Swift 5.7+ allows constrained any/some
// protocol Collection<Element> { ... }
// func process(_ items: some Collection<Int>) { }Try It Yourself!
Create a ShapeFactory with methods returning some Shape. Experiment with what you can and cannot do!