Methods and Subscripts

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.

Methods are functions associated with a particular type — classes, structures and enumerations can all define instance methods and type methods. Subscripts let any type provide []-bracket access to a logical member, the way Array and Dictionary do. Both build directly on Functions, applying the same parameter and return-value rules in a type’s context.

Instance Methods and self

class Counter {
    var count = 0

    func increment() {
        count += 1
    }

    func increment(by amount: Int) {
        count += amount
    }

    func reset() {
        self.count = 0                 // `self` disambiguates the property from a same-named parameter, if any
    }
}

let counter = Counter()
counter.increment()
counter.increment(by: 5)
counter.reset()

An instance method is called with dot syntax on a specific instance and has implicit access to every property and method of that instance; self refers to the current instance and is only required for disambiguation, most commonly inside an initializer parameter (self.count = count) or, on a value type, inside a mutating method that reassigns the whole instance (next).

mutating Methods on Value Types and Assigning to self

struct Point {
    var x = 0.0, y = 0.0

    mutating func moveBy(x deltaX: Double, y deltaY: Double) {
        x += deltaX
        y += deltaY
    }
}

var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
// let fixedPoint = Point(x: 3.0, y: 3.0)
// fixedPoint.moveBy(x: 2.0, y: 3.0)   // error: fixedPoint is a `let` struct -- moveBy cannot be called on it

enum TriStateSwitch {
    case off, low, high

    mutating func next() {
        switch self {
        case .off: self = .low          // a mutating enum method can assign a wholly new case to `self`
        case .low: self = .high
        case .high: self = .off
        }
    }
}

var ovenLight = TriStateSwitch.low
ovenLight.next()                          // .high

A struct or enum method that needs to modify self (or any of its stored properties) must be marked mutating — since instance methods on a value type otherwise cannot modify their properties, unlike a class instance method, which can always mutate its instance’s var properties without any modifier. A mutating method may even assign an entirely new value to self, as TriStateSwitch.next() does; calling a mutating method on a let value-type instance is a compile error, since the instance is fixed and cannot be mutated at all (see Structures and Classes).

Type Methods

class SomeClass {
    class func someTypeMethod() {          // `class`: overridable by a subclass
        print("called on the type itself, not an instance")
    }
}
SomeClass.someTypeMethod()

struct LevelTracker {
    static var highestUnlockedLevel = 1     // `static`: a stored type property

    static func unlock(_ level: Int) {       // `static`: a type method, not overridable (structs can't subclass anyway)
        if level > highestUnlockedLevel { highestUnlockedLevel = level }
    }

    var currentLevel = 1

    mutating func advance(to level: Int) -> Bool {
        if LevelTracker.highestUnlockedLevel >= level {
            currentLevel = level
            return true
        }
        return false
    }
}

A type method is called on the type itself rather than an instance, declared with static func (structs, enums, and non-overridable class methods) or class func (a class method a subclass is permitted to override) — the same static-vs-class split covered for type properties in Properties. Inside a type method, an unqualified reference to another static/class member resolves against the type itself, with no self needed for that purpose.

Subscripts

struct TimesTable {
    let multiplier: Int
    subscript(index: Int) -> Int {             // read-only: single expression body, no `set`
        multiplier * index
    }
}
let threeTimesTable = TimesTable(multiplier: 3)
print(threeTimesTable[6])                      // 18

struct Matrix {
    let rows: Int, columns: Int
    var grid: [Double]

    init(rows: Int, columns: Int) {
        self.rows = rows
        self.columns = columns
        grid = Array(repeating: 0.0, count: rows * columns)
    }

    func indexIsValid(row: Int, column: Int) -> Bool {
        row >= 0 && row < rows && column >= 0 && column < columns
    }

    subscript(row: Int, column: Int) -> Double {           // multi-parameter, multidimensional subscript
        get {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            return grid[(row * columns) + column]
        }
        set {
            assert(indexIsValid(row: row, column: column), "Index out of range")
            grid[(row * columns) + column] = newValue
        }
    }
}
var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
print(matrix[0, 1])                              // 1.5

struct DefaultRow {
    subscript(index: Int, defaultingTo fallback: Int = 0) -> Int {   // a parameter with a default value
        index >= 0 ? index : fallback
    }
}
print(DefaultRow()[-1])                           // 0

enum Planet: Int { case mercury = 1, venus, earth }
extension Planet {
    subscript(name: String) -> Planet? {          // a subscript added retroactively, in an extension
        switch name.lowercased() {
        case "mercury": .mercury
        case "venus": .venus
        case "earth": .earth
        default: nil
        }
    }
}
print(Planet.earth["earth"] as Any)

struct Cosmos {
    static subscript(name: String) -> Int {        // a type subscript, called on the type itself
        name.count
    }
}
print(Cosmos["Earth"])                              // 5

A subscript is declared with subscript(parameters) → ReturnType { }, using the same get/set shorthand as a computed property — a single expression with no get is read-only, and set may be added for a read-write subscript. Subscripts support multiple parameters of any type (including with default values and variadics), letting a single type provide several distinct subscript "overloads" differentiated by parameter type or count, as Matrix’s two-dimensional `[row, column] form shows; they can be declared in an extension exactly like a method or computed property; and a static subscript (or class subscript) applies to the type itself rather than an instance.

Callable Values via callAsFunction, and @dynamicCallable

struct Adder {
    var base: Int
    func callAsFunction(_ x: Int) -> Int {      // lets an Adder value be "called" like a function
        base + x
    }
}
let addFive = Adder(base: 5)
print(addFive(3))                                // 8 -- sugar for addFive.callAsFunction(3)

@dynamicCallable
struct RandomNumberGenerator {
    func dynamicallyCall(withKeywordArguments args: KeyValuePairs<String, Int>) -> Double {
        let lower = args.first(where: { $0.key == "lowerBound" })?.value ?? 0
        let upper = args.first(where: { $0.key == "upperBound" })?.value ?? 1
        return Double.random(in: Double(lower)...Double(upper))
    }
}
let generator = RandomNumberGenerator()
_ = generator(lowerBound: 1, upperBound: 10)      // resolved to dynamicallyCall(withKeywordArguments:) at compile time

callAsFunction lets any instance be invoked with call syntax directly (addFive(3)), useful for types that model a single natural operation, like a mathematical function or a configured transformation. @dynamicCallable is looser still: the compiler rewrites any call syntax on the type into a call to dynamicallyCall(withArguments:) or dynamicallyCall(withKeywordArguments:), whichever the call’s argument shape matches, which is how libraries model dynamic, Python-like calling conventions without knowing the parameter list ahead of time — see Attributes and Compiler Control for @dynamicCallable alongside Swift’s other compiler attributes.

See Also

  • Functions — the parameter/return rules methods and subscripts build on.

  • Structures and Classes — why mutating exists at all: value semantics vs. reference semantics.

  • Properties — static/class type properties, the property-side counterpart to type methods.

  • Extensions and Nested Types — adding methods and subscripts to an existing type from an extension.