Opaque types are a feature of the Swift type system. It can specify an unnamed but specific type that implements a particular protocol.
In this article, we examine:
- What is an opaque type?
- What’s the difference between opaque types, generics, and protocols?
- When to use opaque types?
The overview
Opaque types can be thought of as “specific types that implement a protocol.” Its syntax :some Protocol, for example:
func makeA(a) -> some Equatable { "A" }
Copy the code
Although the specific type is never exposed to the caller of the function, the return value remains strongly typed. The reason for this is that the compiler knows the specific type:
let a = makeA()
let anotherA = makeA()
print(a == anotherA) ✅ The compiler knows that both values are strings
Copy the code
Let’s test whether different types of opaque types that follow the same protocol are equal:
func makeOne(a) -> some Equatable { 1 }
let one = makeOne()
print(a == one) // ❌ Compilation error: 'a' and 'one' are of different types, although both conform to 'Equatable'
Copy the code
The compiler will assume that two opaque types are not equal:
var x: Int = 0
x = makeOne() // ❌ Compilation error: Cannot assign value of type 'some Equatable' to type 'Int'
Copy the code
This function must return the same opaque type each time:
func makeOneOrA(_ isOne: Bool) -> some Equatable {
isOne ? 1 : "A" // ❌ Compilation error: Cannot convert return expression of type 'Int' to return type 'some Equatable'
}
Copy the code
This allows callers to rely on opaque types for type consistency at run time.
From the compiler’s point of view, an opaque type is equivalent to its underlying type. The compiler abstracts it, exposing the type only as a form that conforms to a given set of constraints.
Opaque types and generics
Opaque types are a special type of generics.
Generics are a feature of the Swift language for type-level abstraction. It allows a type to be used in the same way as any other type satisfying a given set of constraints.
Generics are constrained by the caller
func foo<T: Equatable>(a) -> T{... }let x: Int = foo() // T == Int, chosen by caller
let y: String = foo() // T == String, chosen by caller
Copy the code
Opaque types are constrained by the caller:
func bar(a) -> some Equatable{... }let z = bar() // z is abstracted to Equatable. Concrete type is chosen by bar() implementation
Copy the code
Opaque types are sometimes called “reverse generics”
Opaque types and protocols
Opaque types look and behave like protocols. Therefore, it is important to prove the difference between them.
- Cannot return from a function with
Self
orassociatedtype
Required agreement. In contrast, opaque types can:
// Equatable protocol declaration from the Swift standard library
public protocol Equatable {
static func= =(lhs: Self, rhs: Self) -> Bool
}
func makeTwo(a) -> Equatable { 2 } // ❌ Protocol 'Equatable' can only be used as a generic constraint because it has Self or associated type requirements
func makeThree(a) -> some Equatable { 3 } / / ✅
Copy the code
In SwiftUI you’ll find a lot of syntax that uses some and associatedType modifiers. So it’s important to know.
- A function can return different protocol types. Instead, it must return the same opaque type each time:
protocol P {}
extension Int: P {}
extension String: P {}
func makeIntOrString(_ isInt: Bool) -> P { isInt ? 1 : "1" } / / ✅
func makeIntOrStringOpaque(_ isInt: Bool) -> some P { isInt ? 1 : "1" } / / ❌ Compilation error
Copy the code
When to usesome
The keyword
Some keywords are especially useful when designing general-purpose code, such as libraries or domain-specific languages. The underlying types are never exposed to consumers, although they can take advantage of their static properties. Since they are resolved on opaque types, protocols with associated types and Self requirements can be leveraged.
Opaque types can separate the users of a library from the internal implementation of the library.
conclusion
Here’s a summary of the Swift opaque types and some keywords:
- Opaque types can be thought of as protocols with private base types.
- Opaque types are defined by the implementer of the function, not by the caller.
- A function must return the same opaque type each time.
- Allow a
Self
orassociatedtype
的protocol
As a return type