---
title: Extensions
source: 'https://academia.sh/en/courses/swift/extensions'
course: 'The Swift Language'
language: en
updated: '2026-10-21T05:03:12+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Adds methods and computed properties to a standard type with an extension'
    - 'Gives a type protocol conformance through an extension and writes a mutating method'
    - 'Explains that an extension cannot add a stored instance property, so every property it adds is computed'
    - 'Measures that a static let in an extension is set up once, on first access'
---

# Extensions

Measures adding methods, computed properties and protocol conformance to an existing type from the outside; shows that a stored property cannot be added, and the type-level exception.

In the previous lesson the extension of a protocol gave every conforming type a default body, and that body ran again on every read. The concrete form of the same mechanism is extending a type from the outside. You cannot touch the source of a standard type such as `Int` or `String`, but you can write a new method on top of it. This lesson asks when what an extension adds is evaluated.

An **extension** adds new behavior to an existing type without changing its definition. What can be added: methods, computed properties, subscripts, static members, initializers, nested types and protocol conformance. One thing cannot be added, and the weight of this lesson rests on it: a stored instance property.

## The Measurement Setup

`Probe` sits in the first block, in the same form in every lesson.

```swift
// Probe.swift — records when an expression is evaluated
final class Probe {
    let label: String
    private(set) var evaluations = 0
    private(set) var uses = 0
    private var evaluatedBeforeUse = false

    init(_ label: String) {
        self.label = label
    }

    func eval<T>(_ value: T) -> T {
        evaluations += 1
        if uses == 0 { evaluatedBeforeUse = true }
        return value
    }

    func use<T>(_ access: () -> T) -> T {
        uses += 1
        return access()
    }

    var moment: String {
        if evaluations == 0 { return "never" }
        if evaluations > 1 { return "on-every-use" }
        return evaluatedBeforeUse ? "immediately" : "on-first-use"
    }

    func report() {
        print("\(label): \(evaluations) evaluations, \(uses) uses -> \(moment)")
    }
}

func tally(_ probes: [Probe]) {
    for moment in ["immediately", "on-first-use", "on-every-use", "never"] {
        let count = probes.filter { $0.moment == moment }.count
        print("\(moment): \(count)")
    }
}
```

**TS27.** The body of an extension reaches `Probe` as a parameter or as a field of the type being extended; one of these two ways was chosen in the measurements, and binding a global `Probe` to an extension was not tried.

## Everything Added Is Computed

The block below makes four additions to three types. A method and a `mutating` method are added to `Int`, and a method to `String`. To our own `Meter` type we add a computed property and a protocol conformance: the `CustomStringConvertible` conformance gives the type a text representation, because it asks for the `description` property.

```swift
// main.swift — everything added by an extension runs on every use
struct Meter {
    let value: Int
    let tracker: Probe
}

extension Int {
    func cubed(tracking tracker: Probe) -> Int { tracker.eval(self * self * self) }
    mutating func triple() { self *= 3 }
}

extension String {
    func padded(to width: Int, tracking tracker: Probe) -> String {
        tracker.eval(self + String(repeating: ".", count: max(0, width - count)))
    }
}

extension Meter {
    var centimeters: Int { tracker.eval(value * 100) }
}

extension Meter: CustomStringConvertible {
    var description: String { tracker.eval("\(value) m") }
}

let intMethod = Probe("int-method")
let stringMethod = Probe("string-method")
let computedExtra = Probe("added-computed-property")
let conformance = Probe("added-conformance")

_ = intMethod.use { 3.cubed(tracking: intMethod) }
_ = intMethod.use { 4.cubed(tracking: intMethod) }

_ = stringMethod.use { "ab".padded(to: 5, tracking: stringMethod) }
_ = stringMethod.use { "ab".padded(to: 5, tracking: stringMethod) }

let meter = Meter(value: 7, tracker: computedExtra)
_ = computedExtra.use { meter.centimeters }
_ = computedExtra.use { meter.centimeters }

let described = Meter(value: 7, tracker: conformance)
_ = conformance.use { "\(described)" }
_ = conformance.use { "\(described)" }

var counter = 2
counter.triple()
counter.triple()
print(counter)

let all = [intMethod, stringMethod, computedExtra, conformance]
for probe in all { probe.report() }
tally(all)
```

```
18
int-method: 2 evaluations, 2 uses -> on-every-use
string-method: 2 evaluations, 2 uses -> on-every-use
added-computed-property: 2 evaluations, 2 uses -> on-every-use
added-conformance: 2 evaluations, 2 uses -> on-every-use
immediately: 0
on-first-use: 0
on-every-use: 4
never: 0
```

All four additions came out as **on every use**. A method runs on every call; you expect that. What deserves attention is the `centimeters` property: it is read like a field on the type but it is not stored, it is calculated again on every read. The `CustomStringConvertible` conformance is the same: `description` is built again in every string interpolation. A `mutating` method changes a value in place; `counter` became 18 after two triplings. Because this change is the update of a value and not the evaluation of an expression, it needed no separate `Probe`.

## A Stored Property Cannot Be Added

If you try to write a stored instance property in an extension, the compiler stops you. We checked this first: the fragment below did not compile on Swift 6.4, so it stays without output.

```swift
extension Meter {
    var unit = "m"
}
```

The reason lies in the memory layout. An instance of a type takes up as much space as the stored properties in its definition add up to; this size is fixed when the type is defined. A field added from the outside would have to change this size outside the definition, so the rule of the language closes stored instance properties to extensions. This is why every property you add with an extension is, by definition, a computed property: the value does not sit anywhere, it is produced when asked for. This is the reason all of the previous block came out as `on-every-use`. **TS28.** The rejection in this section was verified only for instance-level storage; adding a `lazy` or an observed property to an extension was not tried separately.

## A Constrained Extension

An extension can be added not to every type but only to the types that meet a certain condition. When you write `extension Array where Element: Numeric`, the added members are visible only on arrays whose elements are numeric. A **constrained extension** is the form for types of the `where` condition we saw for protocol extensions in lesson 06. The constraint narrows where the addition applies; it does not change when the added body runs. We measure this by calling the same method on two different array types.

```swift
// main.swift — a constrained extension is added only to arrays that meet the condition
extension Array where Element: Numeric {
    func total(tracking tracker: Probe) -> Element {
        tracker.eval(reduce(0, +))
    }
}

let intTotal = Probe("int-array")
let doubleTotal = Probe("double-array")

let counts = [1, 2, 3]
let weights = [0.5, 1.5]

_ = intTotal.use { counts.total(tracking: intTotal) }
_ = intTotal.use { counts.total(tracking: intTotal) }
_ = doubleTotal.use { weights.total(tracking: doubleTotal) }
_ = doubleTotal.use { weights.total(tracking: doubleTotal) }

let all = [intTotal, doubleTotal]
for probe in all { probe.report() }
```

```
int-array: 2 evaluations, 2 uses -> on-every-use
double-array: 2 evaluations, 2 uses -> on-every-use
```

The same body ran separately on `Int` and `Double` arrays, and in both it ran on every call. The constraint makes sure the `total` method is not visible on a `[String]`; that is a decision made at compile time. The only thing we see at run time is that the method of a constrained extension behaves like an ordinary method: the result is stored nowhere. **TS29.** That the constrained extension is not visible on `[String]` was not verified separately; only the two types that meet the constraint were measured here.

## An Initializer and a Nested Type in an Extension

An extension can add an initializer to a type as well. For structures this has a special benefit: if you write the initializer in the body of the definition, the memberwise initializer the compiler generates disappears; if you write it in an extension, the two stay together. An extension can also add a **nested type**: an `enum`, `struct` or `class` that is defined inside another type and belongs to it. The block below uses both together. The initializer does a `Probe` evaluation at setup time; the nested type carries a method that runs as it is called.

```swift
// main.swift — an initializer and a nested type in an extension
struct Length {
    let meters: Int
}

extension Length {
    init(centimeters: Int, tracking tracker: Probe) {
        self.init(meters: tracker.eval(centimeters / 100))
    }

    enum Unit {
        case meter, kilometer

        func factor(tracking tracker: Probe) -> Int {
            switch self {
            case .meter: tracker.eval(1)
            case .kilometer: tracker.eval(1000)
            }
        }
    }
}

let initProbe = Probe("initializer-in-extension")
let nestedProbe = Probe("nested-type")

let direct = Length(meters: 5)
let converted = Length(centimeters: 700, tracking: initProbe)
_ = initProbe.use { converted.meters }
_ = initProbe.use { converted.meters }
print(direct.meters)

_ = nestedProbe.use { Length.Unit.kilometer.factor(tracking: nestedProbe) }
_ = nestedProbe.use { Length.Unit.kilometer.factor(tracking: nestedProbe) }

let all = [initProbe, nestedProbe]
for probe in all { probe.report() }
```

```
5
initializer-in-extension: 1 evaluations, 2 uses -> immediately
nested-type: 2 evaluations, 2 uses -> on-every-use
```

The memberwise initializer `Length(meters:)` stayed usable despite the initializer in the extension; the two stood side by side. In the initializer in the extension the evaluation was done once, at setup time, and the result was stored in the `meters` field; reading the field twice produced no new evaluation: **immediately**. This does not contradict added properties running on every read. An initializer does not add a property, it fills an existing stored field; the storage is in the `meters` field of the definition. The method of the nested type, on the other hand, ran on every call like an ordinary method: **on every use**. **TS30.** The nested type was measured here as an `enum`; a nested `struct` or `class` added from an extension was not tried separately.

## A Subscript in an Extension

An extension can add not only methods and computed properties but also a **subscript**. A subscript is a member that lets you read a value in the form `value[index]`, and its body works like the getter of a computed property. Below we add to `Array` a read that does not crash on an out-of-range index. A subscript can take extra parameters; we pass our measuring setup through this.

```swift
// main.swift — a subscript in an extension runs on every read
extension Array {
    subscript(safe index: Int, tracking tracker: Probe) -> Element? {
        let found: Element? = indices.contains(index) ? self[index] : nil
        return tracker.eval(found)
    }
}

let subscriptProbe = Probe("subscript")
let names = ["a", "b", "c"]

_ = subscriptProbe.use { names[safe: 1, tracking: subscriptProbe] }
_ = subscriptProbe.use { names[safe: 9, tracking: subscriptProbe] }

subscriptProbe.report()
```

```
subscript: 2 evaluations, 2 uses -> on-every-use
```

Two reads gave two evaluations; the out-of-range index also ran the body, and only the result was `nil`. Although a subscript looks like a property, it does no storing: every `[...]` expression runs the body again, **on every use**. This shows that the rule of the section "Everything Added Is Computed" extends to the subscript as well. **TS31.** Only a read-only subscript was measured here; a subscript that writes (`set`) was not tried.

## A Mutating Method in an Extension

In value types (structures and enumerations), a method that changes `self` is marked `mutating`. A method in an extension obeys the same rule; an extension can add a `mutating` method that is not in the definition. What is changed is the stored field in the definition, so the extension uses the existing storage without adding storage.

```swift
// main.swift — a mutating method in an extension
struct Counter {
    var count = 0
}

extension Counter {
    mutating func bump(tracking tracker: Probe) {
        count += tracker.eval(1)
    }
}

let bumpProbe = Probe("mutating-method")
var counter = Counter()

bumpProbe.use { counter.bump(tracking: bumpProbe) }
bumpProbe.use { counter.bump(tracking: bumpProbe) }

print(counter.count)
bumpProbe.report()
```

```
2
mutating-method: 2 evaluations, 2 uses -> on-every-use
```

The field received two increments in two calls and the value stayed in `counter.count`; the extension added the method itself, not storage. The body ran again on every call: **on every use**. Putting the earlier sections together, the table comes out like this: whatever an extension adds, be it a property, a subscript or a method, the added body runs on every use. The initializer stays outside this table, because it is a setup step and not a read. **TS32.** The `mutating` method was measured only on a structure; it was not verified separately for a class or an enumeration.

## A Static Member in an Extension

Members that belong to the type and are called with the type itself rather than a value are **static members** (`static`). An extension can add these too. In the Properties lesson we saw a `static let`, and it was set up once, on first access. A static method, on the other hand, is an ordinary method; the distinction is the one between a method and a property. The extension below adds to `Length` a factory called from the type itself, and measures it.

```swift
// main.swift — a static method in an extension
struct Length {
    let meters: Int
}

extension Length {
    static func fromKilometers(_ kilometers: Int, tracking tracker: Probe) -> Length {
        Length(meters: tracker.eval(kilometers * 1000))
    }
}

let staticProbe = Probe("static-method")
_ = staticProbe.use { Length.fromKilometers(2, tracking: staticProbe) }
_ = staticProbe.use { Length.fromKilometers(3, tracking: staticProbe) }

staticProbe.report()
```

```
static-method: 2 evaluations, 2 uses -> on-every-use
```

The static method ran on every call: **on every use**. Belonging to a type does not cache its body; in this measurement caching was seen only in a stored static property such as `static let`. So what you write in an extension depends on whether it is computed or stored, and an extension forbids the stored property anyway. A static stored property does not run into this ban, because the storage is in the type itself and not in the instance. **TS33.** The result of the static method produced a new `Length` each time; instance equality or identity was not measured.

## The Bounding Measurement

The ban applies only at the instance level. An extension can add a `static let` to a type, because there is a single copy per type and it does not affect the memory layout. We watch when this property is set up with `print`. `Probe` cannot be used here: `Probe` is not `Sendable`, and in the Swift 6 language mode a global or type-level `let` property asks for a `Sendable` type. The `print` line inside the setup is the counter that shows how many times, and in what order, the setup ran.

```swift
// main.swift — a static let in an extension is set up once, on first access
struct Scale {
    let factor: Int
}

extension Scale {
    static let unit: Int = {
        print("unit built")
        return 100
    }()
}

print("before access")
print(Scale.unit)
print(Scale(factor: 3).factor * Scale.unit)
print(Scale.unit)
```

```
before access
unit built
100
300
100
```

The line `unit built` appeared once, right before the first access. The added `static let` gives the moment **on first use** at the type level: it waits until the first read, is set up once, and is stored afterwards. So the storage that is banned at the instance level in an extension is allowed, and lazy, at the type level. Added instance properties were measured as `on-every-use`, and the added type property as `on-first-use`; the two can stand side by side in the same extension. **TS34.** This observation rests on the order of `print`; that the setup runs once when the first access comes from two places at the same time was not measured here.

## Summary

- An extension adds methods, computed properties, initializers and protocol conformance to an existing type without changing its definition.
- Methods can be added to standard types such as `Int` and `String` as well; a `mutating` method updates the value in place.
- An extension cannot add a stored instance property; every property it adds is computed and runs again on every read.
- A `static let` in an extension is the exception: at the type level, it is set up once, on first access.
- This lesson's moment: **on every use** (a computed property and a method added by an extension).

## Next Step

The extension added behavior to a type, but the added behavior was tied to a single type. The `cubed` you wrote for `Int` has to be written again for `Double`. The next lesson moves on to generics, which let you write the same body once for many types.
