🎓 A1Learner
🔍
0 of 27 lessons completed

Swift Subscripts in Depth

A subscript is a shortcut for reaching a value inside a type through bracket syntax. Subscripts can take several parameters, return an optional for a safe lookup, and support writing as well as reading.

A grid read with two parameters

The Grid type stores its cells as rows of numbers. Its subscript takes a row and a column, and each read pulls one cell out of the matching row.

struct Grid {
    let cells: [[Int]]
    subscript(row row: Int, column column: Int) -> Int {
        return cells[row][column]
    }
}
let grid = Grid(cells: [[2, 4, 6], [8, 10, 12]])
print(grid[row: 0, column: 1])
print(grid[row: 1, column: 2])

Writing through a read write subscript

This Grid subscript has a getter and a setter. The assignment through the brackets changes one stored cell, and the row print shows the new value in place.

struct Grid {
    var cells: [[Int]]
    subscript(row row: Int, column column: Int) -> Int {
        get {
            return cells[row][column]
        }
        set {
            cells[row][column] = newValue
        }
    }
}
var grid = Grid(cells: [[2, 4, 6], [8, 10, 12]])
grid[row: 1, column: 0] = 80
print(grid[row: 1, column: 0])
print(grid.cells[1])

A safe subscript that returns an optional

The SafeList subscript checks the index against the count before reading. An index inside the range returns the stored word, and an index outside it returns nil, which the fallback turns into a plain line.

struct SafeList {
    let items: [String]
    subscript(safe index: Int) -> String? {
        if index >= 0 && index < items.count {
            return items[index]
        }
        return nil
    }
}
let list = SafeList(items: ["Pen", "Paper", "Ruler"])
print(list[safe: 1] ?? "Missing")
print(list[safe: 9] ?? "Missing")

A computed subscript with no storage

The TimesTable type stores nothing at all. Its subscript multiplies the row by the column, so every read is worked out from the two parameters on the spot.

struct TimesTable {
    subscript(row row: Int, column column: Int) -> Int {
        return row * column
    }
}
let table = TimesTable()
for row in 1...3 {
    print(table[row: row, column: 4])
}
print(table[row: 6, column: 7])

Example

struct StockGrid {
    var counts: [[Int]]
    subscript(row row: Int, column column: Int) -> Int {
        get {
            return counts[row][column]
        }
        set {
            counts[row][column] = newValue
        }
    }
}
var stock = StockGrid(counts: [[3, 5], [7, 2]])
stock[row: 0, column: 1] = 6
var total = 0
for row in 0...1 {
    for column in 0...1 {
        total += stock[row: row, column: column]
    }
}
print(stock[row: 0, column: 1])
print(total)
print(stock.counts[1])

Key points

  • A subscript is declared with the subscript keyword inside a type.
  • A subscript can take more than one parameter, and writing a parameter name twice in the declaration gives that parameter a label at the call.
  • A read only subscript has a body that returns a value and nothing else.
  • A subscript with a getter and a setter supports both reading and writing through the brackets.
  • A subscript can return an optional, so a lookup that misses answers nil instead of trapping.
  • A computed subscript can work a value out from its parameters without storing anything.

In this example, the StockGrid subscript reads and writes single cells of a two row grid. The assignment raises the top right cell from 5 to 6 before the loops run. The nested loops add all four cells through the subscript, giving a total of 18, and the last line prints the untouched second row.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A safe subscript returns an optional and the index is outside the stored range. What does the lookup give back?