Opaque Types in Swift
Swift’s some keyword lets you return a value whose concrete type stays hidden while still preserving type safety. It behaves like the inverse of any: callers know there is a specific type, but they only interact with the protocol requirements that type satisfies.
Why Use Opaque Return Types?
- Hide implementation details while keeping static dispatch and optimizations.
- Avoid exposing long generic signatures from helper methods.
- Keep flexibility to swap implementations without breaking API callers.
Basic Example
| |
Opaque types guarantee that each function returns a single concrete type. makeUnitSquare() always yields Square, while makeCircle(radius:) always yields Circle for any radius value.
Opaque Types vs Protocol Existentials (any)
somepreserves the concrete type across the call site (the compiler still knows it), enabling inlining and overload resolution.anyerases the type entirely, so value semantics likeSelfrequirements or associated types become unavailable.- For performance-critical code—such as a frequently reused builder—
sometypically avoids the dynamic dispatch overhead ofany.
Working with Associated Types
Protocols with associated types cannot be used directly as return types. Opaque types let you expose them safely:
| |
Because buildLocalProvider() always returns a LocalProvider, the compiler can resolve Item as String internally without exposing it to callers.
Composing Views
In SwiftUI, opaque types keep view hierarchies short and expressive:
| |
Returning some View from bubble(color:) hides the exact view composition (text + padding + background) while enabling SwiftUI’s diffing engine to optimize the layout.
Swapping Implementations Without Breaking Callers
Opaque types let you change the concrete return type without modifying call sites, as long as the returned value still conforms to the declared protocol:
| |
The compiler rejects this implementation because the function would return two concrete types. A common fix is to wrap the types to ensure a single concrete return:
| |
The key rule: each opaque-returning function must always produce the same concrete type, even though callers only see the protocol surface.
Testing Tips
- Use
type(of:)inside tests to confirm the concrete type stays stable after refactors. - Benchmark
someversusanywhere performance matters; opaque types often remove dynamic dispatch costs. - In SwiftUI previews, keep helper builders
privateand opaque to minimize rebuild churn when iterating on the design.