Logo for tanaschita.com

Understanding opaque types in Swift

Learn how to use opaque return types in Swift and SwiftUI.

07 Feb 2022 · 3 min read

Swift 5.1 introduced a new language feature called opaque types. Opaque types give us the capability to return a concrete type without having to expose it. Let's directly jump into an example to see what that means.

Let's say we have a protocol called Tea. Imagine that different kind of teas might have different identifier types like String or Int, so we add an associatedtype called Identifier.

protocol Tea {
associatedtype Identifier
var id: Identifier { get }
}

Using this protocol, we can now build concrete tea types, for example GreenTea.

struct GreenTea: Tea {
let id: String
init(id: String) {
self.id = id
}
}

Now, we want to build a method, that returns the currently most popular tea from the shop. Since it might change in the future, we don't want to return a concrete tea type, so we decide to return the protocol.

func favoriteTea() -> Tea {
return GreenTea(id: "someId")
}
// Compiler error

The method above will not compile with the error Protocol Tea can only be used as a generic constraint because it has Self or associated type requirements. The compiler doesn’t preserve the type identity of the returned value when using a protocol as return type, so we cannot use the protocol this way.

And here is where an opaque type comes in handy.

func favoriteTea() -> some Tea {
return GreenTea(id: "someId")
}

As we can see above, an opaque type is defined with the some keyword. Returning an opaque type is almost like returning a protocol. In both cases, the caller cannot see the concrete type. The difference is that unlike protocols, an opaque type still refers to a specific type.

However, when using opaque return types, we cannot return different types for example depending on some other value. Which we could do with protocols without associated types.

func favoriteTea() -> some Tea {
someCondition ? GreenTea(id: "someId") : AppleTea(id: 256783)
}
// Compiler error.

This results in a the compiler error Function declares an opaque return type, but the return statements in its body do not have matching underlying types. We could still return different teas of the same concrete type though.

Opaque types in SwiftUI

You may have already noticed that SwiftUI uses opaque return types for building views.

var body: some View {
}

This way, SwiftUI prevents the exact type information of the view hierarchy to be able to do tasks like diffing. And at the same time, we don't need to specify the exact type of this view hierarchy - which can get quite complex.

When working with opaque types in SwiftUI, you may have already stumbled across the problem mentioned earlier.

private var nameView: some View {
if isEditable {
return TextField("Your name", text: $name)
} else {
return Text(name)
}
}

The above code won't compile with the error message Function declares an opaque return type, but the return statements in its body do not have matching underlying types. Solutions on how we can still build a view hierarchy with conditional statements are covered in this article on How to avoid using AnyView in SwiftUI.

Image of books
Further reading: SwiftUI by Example
For further reading, check out the SwiftUI by Example book by Paul Hudson. It guides you through dozens of common coding problems with hands-on solutions.

Newsletter

Receive a monthly newsletter about the latest articles and tips.

Image of a reading marmot

Related tags

Written by

Articles with related topics

Latest articles and tips