Nested Types

Many types are only useful in the context of a larger type. Swift lets you define enumerations, structures, and classes inside the definition of another type. Nesting keeps these helper types neatly scoped, gives them clear names, and signals that they belong together.

Why nest types?

Suppose you're modeling a playing card. A card has a suit and a rank — concepts that have no meaning on their own outside the card. Rather than scatter Suit and Rank across your module's namespace, you nest them within the card type. The result reads naturally and prevents name clashes (your Suit can coexist with someone else's).

Defining nested types

To nest a type, you simply write its definition inside the curly braces of the enclosing type. Nested types can have their own properties, methods, and even further nested types.

struct BlackjackCard {

    // nested Suit enumeration
    enum Suit: Character {
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }

    // nested Rank enumeration
    enum Rank: Int {
        case two = 2, three, four, five, six, seven, eight, nine, ten
        case jack, queen, king, ace

        // a nested struct inside a nested enum
        struct Values {
            let first: Int
            let second: Int?
        }

        var values: Values {
            switch self {
            case .ace:
                return Values(first: 1, second: 11)
            case .jack, .queen, .king:
                return Values(first: 10, second: nil)
            default:
                return Values(first: self.rawValue, second: nil)
            }
        }
    }

    // BlackjackCard's own properties, using the nested types
    let rank: Rank
    let suit: Suit

    var description: String {
        var output = "suit is \(suit.rawValue),"
        output += " value is \(rank.values.first)"
        if let second = rank.values.second {
            output += " or \(second)"
        }
        return output
    }
}

A classic example: Suit, Rank, and even a Values struct all live inside BlackjackCard.

Notice how the nested types reference one another freely. Inside BlackjackCard you write just Rank and Suit — no qualification needed, because they're already in scope.

Using a type with nested types

From inside the enclosing type, nested names are used directly:

let aceOfSpades = BlackjackCard(rank: .ace, suit: .spades)
print("The ace of spades: \(aceOfSpades.description)")
// → The ace of spades: suit is ♠, value is 1 or 11

Because rank and suit are typed as the nested enums, Swift's type inference lets you pass the shorthand .ace and .spades — it knows which enums to look in.

Referring to nested types from outside

To use a nested type from outside its enclosing type, prefix its name with the name of the type it's nested in. You can drill down through multiple levels with more dots.

// Qualify the nested type with its container's name:
let heartsSymbol = BlackjackCard.Suit.hearts.rawValue
print(heartsSymbol)   // → ♡

// Two levels deep: Values is nested inside Rank inside BlackjackCard
let faceValue = BlackjackCard.Rank.Values(first: 10, second: nil)
print(faceValue.first)   // → 10

Tip: if a fully qualified name gets long and you use it often, a typealias can shorten it locally: typealias Suit = BlackjackCard.Suit. The nested definition stays where it logically belongs, but you get a convenient short name.

A more practical example

Nesting isn't just for puzzles. It shines whenever a type carries configuration, state, or error cases that are meaningful only in its context. Here a network client nests both an Endpoint enum and a dedicated Error type:

struct APIClient {
    enum Endpoint: String {
        case users = "/users"
        case posts = "/posts"
    }

    enum RequestError: Error {
        case notFound
        case unauthorized
        case server(code: Int)
    }

    let baseURL: String

    func path(for endpoint: Endpoint) -> String {
        baseURL + endpoint.rawValue
    }
}

let client = APIClient(baseURL: "https://example.com")
print(client.path(for: .users))      // → https://example.com/users

// From outside, qualify the nested error type:
let failure: APIClient.RequestError = .server(code: 503)

The reader immediately understands that APIClient.RequestError describes failures of this client, and the names won't collide with error types elsewhere in your app.

Wrapping up

Nested types let you organize related types together, scope helpers to where they belong, and keep your top-level namespace clean. Inside the enclosing type you refer to nested names directly; from outside you qualify them with the container's name, drilling through each level with a dot. Reach for nesting whenever a type only makes sense as part of a larger one.