Extensions and Nested Types

This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline.

This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

An extension adds new functionality to an existing class, structure, enumeration, or protocol type — including one defined in another module, or even in the standard library — without needing access to its original source code. A nested type is a type declared inside another type, scoping it to that enclosing type’s namespace.

Extension Syntax

extension Double {
    var km: Double { self * 1_000.0 }
    var m: Double { self }
    var cm: Double { self / 100.0 }
}

let oneInch = 25.4.cm
let threeFeet = 3.0 * 12.0.cm
print("One inch is \(oneInch) meters")

extension SomeType { …​ } opens a new block of declarations that are added to SomeType as if they had been written in its original declaration. An extension may adopt one or more protocols at the same time (extension SomeType: SomeProtocol { …​ }), exactly like the type’s own declaration would.

Adding Computed Properties, Initializers, Methods, Subscripts and Nested Types via Extensions

struct Point { var x = 0.0, y = 0.0 }

extension Point {
    init(both value: Double) {                     // extensions can add new initializers...
        self.init(x: value, y: value)               // ...but only convenience-style ones for a class: see below
    }

    func translated(by dx: Double, _ dy: Double) -> Point {    // ...instance and mutating methods...
        Point(x: x + dx, y: y + dy)
    }

    mutating func moveToOrigin() {
        self = Point()
    }

    subscript(index: Int) -> Double {               // ...subscripts...
        index == 0 ? x : y
    }

    enum Quadrant { case i, ii, iii, iv }             // ...and nested types.
}

var p = Point(both: 3.0)
print(p[0], p[1])
p.moveToOrigin()

Extensions can add computed instance and type properties, instance and type methods (including mutating methods on a structure or enumeration), initializers, subscripts, nested types, and protocol conformances — but never stored properties, property observers on properties the extension didn’t declare, or a designated initializer or deinit for a class (a class extension may only add convenience initializers, since the class’s original declaration remains solely responsible for its designated initializers and full stored-property setup; see Initialization and Deinitialization).

Retroactive Conformance and @retroactive

protocol TextRepresentable {
    var textualDescription: String { get }
}

// Conforming a type you don't own (e.g. from another module) to a protocol you don't own either
// is a *retroactive* conformance -- mark it explicitly so the compiler can warn about the risk:
extension Array: @retroactive TextRepresentable where Element: TextRepresentable {
    var textualDescription: String {
        "[" + map(\.textualDescription).joined(separator: ", ") + "]"
    }
}

Adding a protocol conformance to a type via an extension — even a type you don’t own, conforming it to a protocol you don’t own either — is called retroactive conformance, and it is genuinely useful (e.g. teaching a standard-library or third-party type to conform to one of your own protocols). It is also risky: if the type’s own module, or the protocol’s own module, later adds the very same conformance itself, the two conflict at link time. @retroactive makes this explicit at the declaration site so both the compiler and a reader can see the conformance is being asserted from outside, rather than accidentally duplicating one that may appear upstream later.

Organising a Type Across Extensions

struct Employee {
    var name: String
    var salary: Double
}

// Grouping related functionality into its own extension, rather than one large original declaration,
// is a common way to organize a type -- especially one whose declaration lives in a different file.
extension Employee: CustomStringConvertible {
    var description: String { "\(name) (\(salary))" }
}

extension Employee: Equatable {
    static func == (lhs: Employee, rhs: Employee) -> Bool {
        lhs.name == rhs.name && lhs.salary == rhs.salary
    }
}

Splitting a type’s protocol conformances, or a logically distinct group of members, into separate extensions — often one extension per conformance, as above — is a common organizational convention: each extension reads as a self-contained unit (“here is how Employee prints itself”, “here is how Employee compares for equality”) without needing the type’s full original declaration open at the same time. This is purely an organizational device: Swift does not distinguish members added in the original declaration from members added in an extension at the call site.

Nested Types for Namespacing and Scoped Enums

struct BlackjackCard {
    enum Suit: Character {                          // scoped to BlackjackCard -- referred to as BlackjackCard.Suit
        case spades = "♠", hearts = "♡", diamonds = "♢", clubs = "♣"
    }

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

        struct Values {                              // nested two levels deep: BlackjackCard.Rank.Values
            let first: Int
            let second: Int?
        }

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

    let rank: Rank
    let suit: Suit
}

Nesting a type inside another scopes its name to the enclosing type’s namespace — Suit and Rank above exist only as BlackjackCard.Suit and BlackjackCard.Rank from outside BlackjackCard, which avoids polluting the module’s top-level namespace with names (Suit, Rank) that only make sense in the context of a playing card. This is the idiomatic way to give an enum, struct, or class a home when it exists purely to support one other type — a scoped enum, in Suit/`Rank’s case.

Referring to Nested Types

let aceOfSpades = BlackjackCard(rank: .ace, suit: .spades)   // inferred: no need to spell out the enclosing type
print(aceOfSpades.rank.values.first)

let heartsSuit: BlackjackCard.Suit = .hearts                  // spelled out fully, from outside BlackjackCard
let jackValues: BlackjackCard.Rank.Values = BlackjackCard.Rank.jack.values

Inside the enclosing type (or when the expected type is already known, as with rank: .ace above), a nested type’s cases and members can be referred to with the same shorthand .member syntax as any other type. From outside the enclosing type, a nested type’s full name must be qualified with its enclosing type’s name(s), as BlackjackCard.Suit and BlackjackCard.Rank.Values show — exactly as a top-level type’s name would be qualified by its module when needed.

See Also

  • Structures and Classes — the base declarations extensions add to.

  • Protocols — conforming a type to a protocol via an extension, conditional conformance, and protocol extensions with default implementations.

  • Enumerations — the enum features (raw values, associated values, methods) that a nested enum uses just like a top-level one.

  • Access Control — controlling what an extension’s added members, and a nested type itself, expose outside their module.