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.