Skip to Content
Course content

184: Opaque Types with some Keyword

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You've likely seen the some keyword popping up everywhere if you've spent any time with SwiftUI, but it's often glossed over as just "some magic that makes the code compile." In reality, it's a powerful tool for API design called Opaque Types. The goal is simple: I want to tell you that a function returns something that conforms to a protocol, but I want to keep the actual concrete type a secret.

Defining our Drawable interface

Let's imagine we're building a simple graphics engine. We have a protocol called Drawable. Any object that can be rendered to the screen should follow this contract. I'll start by setting up the protocol and a couple of concrete shapes.

protocol Drawable {
    func draw() -> String
}

struct Circle: Drawable {
    func draw() -> String { "Drawing a Circle ◯" }
}

struct Square: Drawable {
    func draw() -> String { "Drawing a Square □" }
}

Now, I want a function that provides a default shape for the user. At first glance, you might think, "Why not just return Drawable?" Let's look at why some Drawable is different.

Hiding the implementation with some

If I return Drawable (the protocol itself), I'm using an existential type. That's fine for some cases, but it erases the specific type identity. By using some Drawable, I'm telling the Swift compiler: "This function returns one specific type that conforms to Drawable, but the caller doesn't need to know which one it is."

func makeDefaultShape() -> some Drawable {
    return Circle()
}

The beauty here is that the caller knows the result is Drawable, but they can't treat it as a Circle. If I decide to change the default shape to a Square tomorrow, I don't have to change a single line of code in the rest of the app. I've successfully decoupled the implementation from the interface.

The "One Type" trap

Here is where I usually trip up when I'm first implementing this, and it's a mistake you'll likely make too. I thought, "Since I'm hiding the type anyway, I can return different shapes based on a condition!" Let's try this:

func makeShape(isRound: Bool) -> some Drawable {
    if isRound {
        return Circle()
    } else {
        return Square()
    }
}

If you try to compile this, Swift will scream at you. You'll get an error saying "Function declares an opaque return type, but the return statements in its body do not have the same underlying type!"

I forgot the golden rule of some: while the caller doesn't know the type, the compiler does. An opaque return type must be one specific type for every possible execution path. You can't switch between a Circle and a Square because the compiler needs to guarantee the identity of that type for the duration of the program's execution.

Keeping the type identity intact

To fix this, I have two choices. I could go back to using the protocol as the return type (existential), but if I want to keep the performance and type-safety benefits of opaque types, I need to wrap my logic. I'll create a wrapper type that conforms to Drawable and handles the switching internally.

struct ShapeWrapper: Drawable {
    let isRound: Bool
    
    func draw() -> String {
        return isRound ? Circle().draw() : Square().draw()
    }
}

func makeShape(isRound: Bool) -> some Drawable {
    // Now we are returning exactly ONE type: ShapeWrapper
    return ShapeWrapper(isRound: isRound)
}

By doing this, I've satisfied the compiler. The return type is now consistently ShapeWrapper, but the user of the function still just sees some Drawable. This is exactly how SwiftUI's View works—it wraps a massive tree of different types into one single opaque some View.




📋 Practical Task

Build a Theme-Based Component Provider

You are building a UI library that supports different themes. Create a protocol named Themeable with a property primaryColor (a String). Create two concrete structs, LightTheme and DarkTheme, that conform to this protocol.

Your task is to write a function called provideTheme(isDarkMode: Bool) that returns some Themeable. To avoid the "One Type" trap we discussed in the lesson, you must implement a ThemeWrapper struct that handles the conditional logic internally. Ensure that the function returns the wrapper, maintaining the opaque return type.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.