Understanding Subscripts

abdul ahad
4 min readAug 21, 2024

--

Photo by Toa Heftiba on Unsplash

Subscripts in Swift allow you to access elements of a collection, list, or sequence in a more concise way, similar to how you access elements in an array or dictionary using brackets (e.g., array[index] or dictionary[key]). However, subscripts are not limited to just arrays or dictionaries;

1. Subscripts in Swift

Subscripts in Swift are a hybrid between functions and properties, allowing you to access elements in a collection-like manner using special syntax. They can be read-only (with just a get method) or read-write (with both get and set methods).

Example: Standard Subscript

let array = [0, 1, 1, 2, 3, 5]
let firstElement = array[0] // 0
let slice = array[1..<3] // [1, 1]

Here, array[0] uses a subscript to access the first element of the array, and array[1..<3] returns a slice of the array.

2. Custom Subscripts

You can define your own subscripts for custom types, or extend existing types with additional subscript functionality.

Example: Custom Subscript for a Collection

Let’s define a custom subscript for any Collection that allows you to pass multiple indices and get an array of elements at those indices.

extension Collection {
subscript(indices indexList: Index...) -> [Element] {
var result: [Element] = []
for index in indexList {
result.append(self[index])
}
return result
}
}

// Usage
let letters = Array("abcdefghijklmnopqrstuvwxyz")
let hello = letters[indices: 7, 4, 11, 11, 14] // ["h", "e", "l", "l", "o"]

Here, the subscript takes a variadic parameter indexList, allowing you to pass a comma-separated list of indices. The subscript returns an array of elements corresponding to those indices.

3. Advanced Subscripts

Subscripts in Swift can take multiple parameters, and they can be generic, allowing for more complex and powerful functionality.

Example: Dictionary with Multiple Parameters

Consider a dictionary that stores different types of data under various keys:

var japan: [String: Any] = [
"name": "Japan",
"capital": "Tokyo",
"population": 126_440_000,
"coordinates": [
"latitude": 35.0,
"longitude": 139.0
]
]

If you want to update the latitude value in the coordinates dictionary, it’s not straightforward:

// This won’t work:
// japan[“coordinates”]?[“latitude”] = 36.0

The problem is that japan["coordinates"] returns an Any?, which doesn’t have subscripts.

You might try to cast the value first:

// This also won’t work:
// (japan[“coordinates”] as? [String: Double])?[“latitude”] = 36.0

The above fails because you can’t mutate a value through a typecast.

Solution: Custom Generic Subscript

You can extend Dictionary with a custom subscript that handles this:

extension Dictionary {
subscript<Result>(key: Key, as type: Result.Type) -> Result? {
get {
return self[key] as? Result
}
set {
guard let value = newValue else {
self[key] = nil
return
}
guard let value2 = value as? Value else {
return
}
self[key] = value2
}
}
}

// Usage
japan["coordinates", as: [String: Double].self]?["latitude"] = 36.0
print(japan["coordinates"])
// Output: Optional(["latitude": 36.0, "longitude": 139.0])

In this custom subscript:

  • The subscript takes two parameters: a key and a type to cast to.
  • The get method attempts to cast the value stored under key to the specified type (Result).
  • The set method allows for safely updating the value if it matches the expected type.

4. Limitations and Alternatives

While the above solution allows for more flexible handling of heterogeneous dictionaries, the syntax can become cumbersome and error-prone. Swift’s type safety doesn’t naturally lend itself well to such dynamic structures.

Better Alternative: Define Custom Types

In many cases, it’s better to define a custom type, such as a struct, for your data and use properties instead of relying on dynamic types like [String: Any].

Example: Custom Struct

struct Coordinates {
var latitude: Double
var longitude: Double
}

struct Country {
var name: String
var capital: String
var population: Int
var coordinates: Coordinates
}
var japan = Country(
name: "Japan",
capital: "Tokyo",
population: 126_440_000,
coordinates: Coordinates(latitude: 35.0, longitude: 139.0)
)

japan.coordinates.latitude = 36.0
print(japan.coordinates.latitude) // Output: 36.0

this approach is more type-safe, easier to read, and generally better practice in Swift.

Summary

  • Subscripts allow you to access elements in collections using a familiar syntax like array[index].
  • You can create custom subscripts for your types or extend existing types with new subscript functionality.
  • Advanced subscripts can be generic and take multiple parameters, allowing for powerful operations, like handling dynamic types in dictionaries.
  • While powerful, using dynamic types ([String: Any]) in Swift can lead to cumbersome code, and you might be better off defining custom types for better safety and clarity

References

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning

No responses yet