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.

  1. Cannot return from a function withSelforassociatedtypeRequired 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.

  1. 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 usesomeThe 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 aSelforassociatedtypeprotocolAs a return type