Swift Subscripts

A subscript is a shortcut for reading and writing items inside a collection-like type. You already use subscripts with arrays and dictionaries, as in names[0] or ages["Mia"]. Swift lets you add the same square-bracket access to your own structs, classes, and enums.

Think of a wall of numbered lockers at a gym. Give the attendant a locker number and the attendant opens the correct door. A subscript works as the attendant: it accepts an index and returns the matching item.

Diagram: Locker Wall

 Grid with 2 rows and 3 columns, stored in one list

 grid[row, column]       Position in the list

 grid[0, 0]  grid[0, 1]  grid[0, 2]      0   1   2
 grid[1, 0]  grid[1, 1]  grid[1, 2]      3   4   5

 Formula: position = row * columns + column

Basic Syntax

Write a subscript like a function that has no name. Use the keyword subscript, add parameters, and declare the return type.

struct TimesTable {
    let multiplier: Int

    subscript(index: Int) -> Int {
        multiplier * index
    }
}

let threes = TimesTable(multiplier: 3)
print(threes[4])   // Output: 12
print(threes[10])  // Output: 30

A subscript with a single expression is read-only. Callers can look up values and cannot assign new ones.

Read-Write Subscripts

Add a get block and a set block to allow changes. The setter receives the incoming value as newValue.

struct Scoreboard {
    private var scores = [0, 0, 0]

    subscript(player: Int) -> Int {
        get {
            scores[player]
        }
        set {
            scores[player] = newValue
        }
    }
}

var board = Scoreboard()
board[1] = 25
print(board[1])   // Output: 25
print(board[0])   // Output: 0

Subscripts With Several Parameters

A subscript accepts more than one parameter. This design fits tables, grids, and game boards.

struct Grid {
    let rows: Int
    let columns: Int
    private var cells: [Int]

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

    subscript(row: Int, column: Int) -> Int {
        get {
            cells[row * columns + column]
        }
        set {
            cells[row * columns + column] = newValue
        }
    }
}

var grid = Grid(rows: 2, columns: 3)
grid[1, 2] = 9
print(grid[1, 2])   // Output: 9
print(grid[0, 0])   // Output: 0

The caller sees a clean grid[1, 2] expression. The internal list and the position formula stay hidden.

Argument Labels

Subscript parameters have no external labels by default. Add a label before the parameter name when clarity needs it.

struct Playlist {
    var songs = ["Sunrise", "Ocean", "Midnight"]

    subscript(song position: Int) -> String {
        songs[position]
    }
}

let list = Playlist()
print(list[song: 1])   // Output: Ocean

Safe Access With an Extension

Reading an array position that does not exist crashes the program. A custom subscript returns an optional and removes the risk.

extension Array {
    subscript(safe index: Int) -> Element? {
        indices.contains(index) ? self[index] : nil
    }
}

let colors = ["Red", "Green", "Blue"]
print(colors[safe: 1] ?? "none")   // Output: Green
print(colors[safe: 9] ?? "none")   // Output: none

Type Subscripts

A type subscript belongs to the type itself. Declare it with static and call it on the type name.

enum Planet: Int {
    case mercury = 1, venus, earth

    static subscript(order: Int) -> Planet? {
        Planet(rawValue: order)
    }
}

print(Planet[3] ?? .mercury)   // Output: earth

When Subscripts Fit

Use a subscript when your type behaves like a container of items found by a key or a position. A game board, a spreadsheet, a calendar of events, and a table of translations all qualify. Use a method when the action changes state or does heavy work. Readers expect a subscript to feel as cheap as reading an array.

Subscripts and Safety

A subscript can validate its input before touching storage. A grid might check that the row and column fall inside the board and report a clear message on failure. Defensive checks inside the subscript protect every caller with a single piece of code.

struct Board {
    private var cells = Array(repeating: "-", count: 9)

    subscript(position: Int) -> String? {
        guard cells.indices.contains(position) else { return nil }
        return cells[position]
    }
}

let board = Board()
print(board[4] ?? "invalid")    // Output: -
print(board[12] ?? "invalid")   // Output: invalid

Common Mistakes

A subscript without a setter is read-only, and assignment through it triggers an error. Add a set block when callers must write values. Forgetting that subscript parameters carry no labels by default leads to calls that fail to compile.

Real-World Use

Tic-tac-toe boards, chess boards, and puzzle grids use two-parameter subscripts. Translation tables use string subscripts that map a word to its meaning. Custom array types use integer subscripts to hide how data is stored.

Subscripts in the Standard Library

Swift types use subscripts everywhere. Arrays take an integer position. Dictionaries take a key and return an optional value. Strings take a special index type, because characters differ in size and a plain integer position would be slow and misleading. Studying these designs shows how a subscript should behave: quick, predictable, and free of surprises.

Key Takeaways

  • Subscripts add square-bracket access to custom types.
  • A get block reads values, and a set block writes them.
  • Subscripts accept multiple parameters and optional labels.
  • Extensions can add safe subscripts to existing types.