Opaque Return Types is a new feature released in Swift 5.1 by Apple. It can be used to return some
values for method / function and property
without property
disclosing the type of value to the caller. The return type will be some
of a protocol
. Using this method, the module does not need to explicitly return the type and content of the method, it only needs to return the opaque type of a protocol with the keyword some
. The Swift compiler can also retain the identity
of return types unlike the use of protocol
as return types. SwiftUI
uses the opaque return type within it. View
protocol will return some View
in the body
of the property.
Here are some essentials that opaque returns provide to keep in our toolbox and take advantage of whenever we want to create APIs with Swift:
- Provide a specific type of protocol without revealing the
concrete
type to the API caller for betterencapsulation
. - Because the API does not expose a specific return type to the caller, the caller does not need to worry about if the underlying type is changed in the future as long as it implements the base protocol.
- Provides a strong guarantee of identity by returning a specific type at run time. The tradeoff is losing the flexibility of returning multiple types of recommended values because using the
protocol
as a return type. - Because the guarantee will return to a specific protocol. The function can return
opaque
protocol
type if it hasSelf
or requiresassociated type
. - The
protocol
leaves the decision about the type of return to the caller. Andopaque
is the opposite. It is thefunction
determines the specific return type as long as it implements theprotocol
.
Take a look at an example of using Opaque return type.
To better understand opaque
return type and why it is different from just using protocol
as a return type.
Define a protocol with associatedtype
We have a protocol
called MobileOS
. This Protocol
has an associatedtype
called Version
and a property
to get Version
for a specific type to implement.
1 2 3 4 5 6 | <span class="token keyword">protocol</span> <span class="token builtin">MobileOS</span> <span class="token punctuation">{</span> associatedtype <span class="token builtin">Version</span> <span class="token keyword">var</span> version <span class="token punctuation">:</span> <span class="token builtin">Version</span> <span class="token punctuation">{</span> <span class="token keyword">get</span> <span class="token punctuation">}</span> <span class="token keyword">init</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token builtin">Version</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Implement type specific to the protocol
Define two specific types for this protocol, called iOS
and Android
. Both have differences in the semantics of Version
. iOS
uses type float
while Android
uses String
1 2 3 4 5 6 7 | <span class="token keyword">struct</span> iOS <span class="token punctuation">:</span> <span class="token builtin">MobileOS</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> version <span class="token punctuation">:</span> <span class="token builtin">Float</span> <span class="token punctuation">}</span> <span class="token keyword">struct</span> <span class="token builtin">Android</span> <span class="token punctuation">:</span> <span class="token builtin">MobileOS</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> version <span class="token punctuation">:</span> <span class="token builtin">String</span> <span class="token punctuation">}</span> |
Create a function to return a Protocol type
We want to return a function to return a MobileOS
protocol as a return type. In the usual way we would write like this:
Solution 1 (Returns Protocol Type):
1 2 3 4 5 6 | <span class="token keyword">func</span> <span class="token function">buildPreferredOS</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">MobileOS</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">iOS</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token number">13.1</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Compiler ERROR </span> <span class="token builtin">Protocol</span> <span class="token string">'MobileOS'</span> can only be used <span class="token keyword">as</span> a generic constraint because it has <span class="token keyword">Self</span> or associated type requirements |
As you can see, the build failed because the protocol used associatedtype
. The compiler does not retain the identity type of the return value when using the protocol as a return type. So now let’s try a few ways when returning a specific type.
Solution 2 (Returns Concrete Type):
1 2 3 4 5 | <span class="token keyword">func</span> <span class="token function">buildPreferredOS</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> iOS <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">iOS</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token number">13.1</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Build successfully</span> |
This method seems to work. But you can see the caller can see the specific type. This code will probably need a lot of refactor times if in the future you want to change something like return Android
as the function’s return type.
Solution 3 (Generic Function Return)
1 2 3 4 5 6 | <span class="token keyword">func</span> buildPreferredOS <span class="token operator"><</span> T <span class="token punctuation">:</span> <span class="token builtin">MobileOS</span> <span class="token operator">></span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> T <span class="token punctuation">.</span> <span class="token builtin">Version</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> T <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">T</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> version <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">let</span> android <span class="token punctuation">:</span> <span class="token builtin">Android</span> <span class="token operator">=</span> <span class="token function">buildPreferredOS</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token string">"Jelly Bean"</span> <span class="token punctuation">)</span> <span class="token keyword">let</span> ios <span class="token punctuation">:</span> iOS <span class="token operator">=</span> <span class="token function">buildPreferredOS</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token number">5.0</span> <span class="token punctuation">)</span> |
This approach seems more “professional”. But now the caller must provide a specific type for the function to return. But if you want the caller to really not care about the specific return type, then this method is not quite right.
Final Solution (Opaque Return Type to the rescue)
1 2 3 4 | <span class="token keyword">func</span> <span class="token function">buildPreferredOS</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> some <span class="token builtin">MobileOS</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token function">iOS</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token number">13.1</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> |
Using opaque return type, we can finally return MobileOS
as a function’s return type. The compiler maintains the specific identity of the return type here and the caller doesn’t need to know the inner type of the return type as long as it implements the MobileOS protocol.
Opaque returns types can only return a specific type.
You might think that like the protocol return type, we can also return different specific types inside the opaque return type as follows:
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token keyword">func</span> <span class="token function">buildPreferredOS</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> some <span class="token builtin">MobileOS</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> isEven <span class="token operator">=</span> <span class="token builtin">Int</span> <span class="token punctuation">.</span> <span class="token function">random</span> <span class="token punctuation">(</span> <span class="token keyword">in</span> <span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token number">100</span> <span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token keyword">return</span> isEven <span class="token operator">?</span> <span class="token function">iOS</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token number">13.1</span> <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token function">Android</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token string">"Pie"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Compiler ERROR </span> <span class="token builtin">Cannot</span> convert <span class="token keyword">return</span> expression of type <span class="token string">'iOS'</span> to <span class="token keyword">return</span> type <span class="token string">'some MobileOS'</span> <span class="token keyword">func</span> <span class="token function">buildPreferredOS</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> some <span class="token builtin">MobileOS</span> <span class="token punctuation">{</span> <span class="token keyword">let</span> isEven <span class="token operator">=</span> <span class="token builtin">Int</span> <span class="token punctuation">.</span> <span class="token function">random</span> <span class="token punctuation">(</span> <span class="token keyword">in</span> <span class="token punctuation">:</span> <span class="token number">0</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token number">100</span> <span class="token punctuation">)</span> <span class="token operator">%</span> <span class="token number">2</span> <span class="token operator">==</span> <span class="token number">0</span> <span class="token keyword">return</span> isEven <span class="token operator">?</span> <span class="token function">iOS</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token number">13.1</span> <span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token function">iOS</span> <span class="token punctuation">(</span> version <span class="token punctuation">:</span> <span class="token number">13.0</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// Build Successfully </span> |
The compiler will return an error if you try to return a different type for the opaque return value. You can only return different types of values for the same specific type.
Simplify complexity and nested types into opaque return types
The final solution is an example of how to leverage the Opaque
return type to hide complexity and nested types into a simple opaque protocol type that can be exposed to the caller.
Consider a function that accepts an array using common constraints for its element to comply with the numeric protocol. This array does a number of things:
- Get the first and last element of the array
- Lazily map function cleverly by performing its own operations for each element.
The caller of this function does not need to know the return type, the caller only needs to loop and print the value.
Try doing this using the function.
Solution 1: Using Generic Return Function
1 2 3 4 5 6 7 | <span class="token keyword">func</span> sliceFirstAndEndSquareProtocol <span class="token operator"><</span> T <span class="token punctuation">:</span> <span class="token builtin">Numeric</span> <span class="token operator">></span> <span class="token punctuation">(</span> array <span class="token punctuation">:</span> <span class="token builtin">Array</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> <span class="token builtin">LazyMapSequence</span> <span class="token operator"><</span> <span class="token builtin">ArraySlice</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">,</span> T <span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">return</span> array <span class="token punctuation">.</span> <span class="token function">dropFirst</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">dropLast</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token keyword">lazy</span> <span class="token punctuation">.</span> <span class="token builtin">map</span> <span class="token punctuation">{</span> $ <span class="token number">0</span> <span class="token operator">*</span> $ <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token function">sliceFirstAndEndSquareProtocol</span> <span class="token punctuation">(</span> array <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token number">2</span> <span class="token punctuation">,</span> <span class="token number">3</span> <span class="token punctuation">,</span> <span class="token number">4</span> <span class="token punctuation">,</span> <span class="token number">5</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> forEach <span class="token punctuation">{</span> <span class="token function">print</span> <span class="token punctuation">(</span> $ <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 9</span> <span class="token comment">// 16</span> |
As you can see, the return type of this function is complex and nested LazyMapSequence<ArraySlice<T>, T>
while the user uses it only to loop and print for each element.
Solution 2: Simple Opaque Return Types
1 2 3 4 5 6 | <span class="token keyword">func</span> sliceHeadTailSquareOpaque <span class="token operator"><</span> T <span class="token punctuation">:</span> <span class="token builtin">Numeric</span> <span class="token operator">></span> <span class="token punctuation">(</span> array <span class="token punctuation">:</span> <span class="token builtin">Array</span> <span class="token operator"><</span> T <span class="token operator">></span> <span class="token punctuation">)</span> <span class="token operator">-</span> <span class="token operator">></span> some <span class="token builtin">Sequence</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> array <span class="token punctuation">.</span> <span class="token function">dropFirst</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token function">dropLast</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> <span class="token keyword">lazy</span> <span class="token punctuation">.</span> <span class="token builtin">map</span> <span class="token punctuation">{</span> $ <span class="token number">0</span> <span class="token operator">*</span> $ <span class="token number">0</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token function">sliceHeadTailSquareOpaque</span> <span class="token punctuation">(</span> array <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token number">3</span> <span class="token punctuation">,</span> <span class="token number">6</span> <span class="token punctuation">,</span> <span class="token number">9</span> <span class="token punctuation">]</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> forEach <span class="token punctuation">{</span> <span class="token function">print</span> <span class="token punctuation">(</span> $ <span class="token number">0</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token comment">// 36</span> |
Using this method, the user doesn’t need to know the return type of the function as long as it matches the sequence
protocol the user uses.
Opaque Return Types in SwiftUI
SwiftUI also relies heavily on this approach, so the body
in View
doesn’t need to be explicit about the return type as long as the View
protocol is followed. Otherwise, the return type inferred can be very nested and complex like this.
1 2 3 4 5 6 7 8 9 | <span class="token keyword">struct</span> <span class="token builtin">Row</span> <span class="token punctuation">:</span> <span class="token builtin">View</span> <span class="token punctuation">{</span> <span class="token keyword">var</span> body <span class="token punctuation">:</span> some <span class="token builtin">View</span> <span class="token punctuation">{</span> <span class="token builtin">HStack</span> <span class="token punctuation">{</span> <span class="token function">Text</span> <span class="token punctuation">(</span> <span class="token string">"Hello SwiftUI"</span> <span class="token punctuation">)</span> <span class="token function">Image</span> <span class="token punctuation">(</span> systemName <span class="token punctuation">:</span> <span class="token string">"star.fill"</span> <span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
The return type of body
would be:
1 2 | <span class="token builtin">HStack</span> <span class="token operator"><</span> <span class="token builtin">TupleView</span> <span class="token operator"><</span> <span class="token punctuation">(</span> <span class="token builtin">Text</span> <span class="token punctuation">,</span> <span class="token builtin">Image</span> <span class="token punctuation">)</span> <span class="token operator">></span> <span class="token operator">></span> |
It is quite complex and nested, remember that it will also change whenever we add a new nested view inside HStack. The Opaque return type really shines in SwiftUI implementations, users don’t really care about the specific type within the View
long as the return type conforms to the View
protocol.
The article about Opaque Return Type is over. Thank you for reading.
Source: https://medium.com/@alfianlosari/understanding-opaque-return-types-in-swift-9c36fb5dfa86