« 'Opaque' Types in Swift 5.1

June 5, 2019 • ☕️ 2 min read

SwiftSwiftUI

In Swift 5.1 and SwiftUI you’ll be seeing a new some keyword popping up. What does it mean? What does it do? That’s what this blog post will be describing. Just before we get started here’s a quick example of its usage:

struct ContentView: View {
    static var body: some View {
        Text("Some text")
    }
}

Technical Description

So ‘opaque result types’ AKA some T are very techincally described in the Swift Evolution Proposal 0244. If you are a technical person definitely go ahead and read that, however if you aren’t immediately familiar with some of the quirks of Swift’s generics you’ll still have quite a few questions about its purpose. The question I’ve seen asked over and over again is:

Why can’t I use -> T instead of -> some T

and the answer is: some T is neither T nor a subclass of T. To be specific when you write -> some T. The Swift compiler restricts your function to return a subclass of T. (where String: T and Int: T):

func getSomeValue(flip: Bool) -> some T {
  if flip { return 17 }
  return "a string" // error: different return types Int and String
}

You may have a lot of questions: what is the point of this? Why is this any use? Those are great questions so let’s get started on some thought experiments (gonna be based on the SEP-0244 but I’ll try to explain it more clearly).

Let’s say I have a function that returns some value of type S (where S: T) but the user doesn’t need to know it’s S. The fact that it is S is an implementation detail so I which to return it as T. If I write this function as returning -> T the issue is, when this return value of my function is used, other functions won’t know that two return values of my function are actually the same concrete type or subclass/implementation. Instead they’ll just see T. So a way to think about it is:

some T let’s Swift know what specific subclass of T something is but it does not let the user

Example

The best way to illustrate this is an example…

Let’s say I have a protocol Animal and I want to compare if two animals are siblings:

protocol Animal {
    func isSibling(with animal: Self) -> Bool
}

This way it only makes sense to compare if two animals are siblings if they are the same type of animal. Now let me just create an example of an animal just for reference

class Dog: Animal {
    func isSibling(with animal: Dog) -> Bool {
        return true // doesn't really matter implementation of this
    }
}

The way without some T

Now let’s say I have a function that returns an animal from a ‘family’.

func animalFromAnimalFamily() -> Animal {
    return randomDog // myDog is just some random variable of type `Dog`
}

Note: this function won’t actually compile, for the reason that it would cause the error described below, but let’s say it does… pretending this upcasts myDog to abstract type Animal, let’s see what happens.

Now the issue comes is if I try to do this:

let animal1: Animal = animalFromAnimalFamily()
let animal2: Animal = animalFromAnimalFamily()

animal1.isSibling(animal2) // error

This will throw an error.

Why? Well the reason is, when you call animal1.isSibling(animal2) Swift doesn’t know if the animals are dogs, cats, or whatever. As far as Swift knows, animal1 and animal2 could be unrelated animal species. Since we can’t compare animals of different types (see above).

How some T solves this problem

Let’s rewrite the previous function:

func animalFromAnimalFamily() -> some Animal {
    return randomDog
}
let animal1 = animalFromAnimalFamily()
let animal2 = animalFromAnimalFamily()

animal1.isSibling(animal2)

animal1 and animal2 are not Animal, but they are class that implements Animal.

What this lets you do now is when you call animal1.isSibling(animal2), Swift knows that animal1 and animal2 are the same type.

Conclusion

So to summarize, some T does solve a very specific problem but it’ll definetly crop up if you’re doing some serious generic work. If you’ve ever used (or seen for that matter) C++ code you’ll be used to seeing insane generics. Swift’s new some T feature will hopefully alleviate that insane aspect and allow for cleaner code while at the same time being performant and descriptive.