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.