Skip to Content
Course content

108: Subscripts in Swift

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember the first time I tried to build a coordinate-based system for a small strategy game in Swift. I had a class to manage the grid, and inside that class, I had a 2D array holding the map data. It seemed obvious that I should be able to access a tile using square brackets, like map[x, y]. But when I tried, the compiler hit me with a wall of red: "Value of type 'GameMap' cannot be subscripted."

struct GameMap {
    var grid = [["Forest", "Forest", "Mountain"],
                ["Forest", "Water", "Forest"],
                ["Mountain", "Forest", "Forest"]]
}

var myMap = GameMap()
// This is what I wanted to do:
let tile = myMap[1, 1] // ❌ Error: Value of type 'GameMap' cannot be subscripted

The missing link between the class and the array

The mistake here is a common assumption: thinking that if a class contains a collection, the class itself automatically inherits the ability to be subscripted. It doesn't. Swift is very explicit about this. The grid property is subscriptable because it's an Array, but myMap is an instance of GameMap, and until you tell Swift how to handle square brackets for that specific type, it has no idea what you're trying to do.

You could "fix" this by accessing the property directly—myMap.grid[1][1]—but that's leaky abstraction. If you later decide to change your internal storage from a 2D array to a dictionary or a flat array for performance reasons, you'd have to hunt down every single .grid[x][y] call in your entire codebase and change it.

Mapping indices with the subscript keyword

To fix this properly, we use the subscript keyword. This allows us to define a custom getter (and optionally a setter) that triggers whenever square brackets are used on an instance of the type. The coolest part? Subscripts can take multiple arguments, which is perfect for grids.

struct GameMap {
    var grid = [["Forest", "Forest", "Mountain"],
                ["Forest", "Water", "Forest"],
                ["Mountain", "Forest", "Forest"]]

    // We define the subscript here
    subscript(row: Int, col: Int) -> String {
        get {
            return grid[row][col]
        }
        set {
            grid[row][col] = newValue
        }
    }
}

var myMap = GameMap()
print(myMap[1, 1]) // ✅ Prints "Water"
myMap[0, 0] = "City" // ✅ Updates the grid via the setter

By adding that block, we've created a shortcut. Now, myMap[1, 1] is actually just a syntactic sugar for calling a function we defined. I personally love this approach because it keeps the "how" (the internal array) hidden and the "what" (getting a tile at a coordinate) clean.

Handling the "Out of Bounds" crash

If you use the code above and call myMap[10, 10], your app will crash. That's because we're passing the index straight to the internal array, which throws a fatal error if the index is out of range. When you write your own subscripts, you have the power to make them safer than standard arrays.

You can make a subscript return an optional, allowing you to handle missing data gracefully instead of crashing the program. Here is how I usually handle this in production code:

subscript(row: Int, col: Int) -> String? {
    get {
        // Check if the row exists first
        guard row >= 0 && row < grid.count else { return nil }
        // Then check if the column exists in that row
        guard col >= 0 && col < grid[row].count else { return nil }
        
        return grid[row][col]
    }
    set {
        // Only set the value if the coordinates are actually valid
        if row >= 0 && row < grid.count && col >= 0 && col < grid[row].count {
            grid[row][col] = newValue ?? "Unknown"
        }
    }
}

Now, myMap[10, 10] simply returns nil. It's a small change, but it's the difference between a professional tool and a buggy prototype.




📋 Practical Task

Building a Safe Configuration Manager

Imagine you are building a settings system for an app. You want to be able to access configuration values using a string key (like config["theme"]), but you want the subscript to return a default value if the key doesn't exist, rather than returning an optional or crashing.

Your Task: Create a struct called AppConfig that meets these requirements:

  • It should have a private dictionary called settings that stores String keys and String values.
  • Implement a read-only subscript that takes a String.
  • If the key exists in the dictionary, return the value.
  • If the key does not exist, return the string "Not Set".

Test your code with this:

let config = AppConfig()
print(config["api_url"]) // Should print "Not Set"
Rating
0 0

There are no comments for now.

to be the first to leave a comment.