Subscripts in Swift provide a convenient syntax for accessing elements of a collection, container, or user-defined type using indices, similar to arrays and dictionaries. They can be declared and overridden in custom classes, structs, and enums.
Subscripts were introduced in Swift as a way to unify indexed access to objects, starting with collections and then extending to any user-defined types. The key goal is to enhance expressiveness and readability, turning data access methods into a "natural" syntax that resembles array usage.
In other languages (e.g. Java), accessing an element of a structure is done through get/set methods, making the code verbose. The quest for better readability and easier data access led to subscripts. In Swift, special attention is required for safe access, getters and setters, possible errors, and optional return values.
A subscript can be declared in any class, struct, or enum using the subscript keyword. Additionally, multiple overloads by parameters and return types are supported.
Example of a user-defined subscript:
struct Matrix { let rows: Int, columns: Int private var grid: [Double] init(rows: Int, columns: Int) { self.rows = rows self.columns = columns grid = Array(repeating: 0.0, count: rows * columns) } subscript(row: Int, column: Int) -> Double { get { precondition(row >= 0 && row < rows) precondition(column >= 0 && column < columns) return grid[(row * columns) + column] } set { precondition(row >= 0 && row < rows) precondition(column >= 0 && column < columns) grid[(row * columns) + column] = newValue } } } var matrix = Matrix(rows: 2, columns: 2) matrix[1, 1] = 3.14 print(matrix[1, 1]) // 3.14
Key Features:
get or get/setCan a subscript be declared with only a getter, without a setter?
Yes. If a subscript is needed for read-only access, it is sufficient to implement only get.
struct ReadOnlyArray { private let items = [1, 2, 3] subscript(index: Int) -> Int { get { return items[index] } } }
Is it allowed to declare multiple subscripts with different signatures in the same type?
Yes, subscripts can be overloaded by the number and type of parameters.
struct Collection { subscript(index: Int) -> String { ... } subscript(key: String) -> Int? { ... } }
Can a subscript be declared in a protocol and implemented in a struct/class?
Yes, protocols can require the implementation of a subscript, and concrete types must implement them.
protocol MySubscriptable { subscript(index: Int) -> String { get set } } struct SubData: MySubscriptable { private var items = ["a", "b"] subscript(index: Int) -> String { get { return items[index] } set { items[index] = newValue } } }
A developer implemented a subscript without boundary checking. As a result, accessing a non-existent index caused the application to crash with a runtime error.
Pros: The code became more concise.
Cons: The application started to crash due to unexpected errors.
Implementation of a subscript with precondition checks and the ability to return optional values or throw errors.
Pros: The code became safer for users, and errors can be handled.
Cons: Additional logic for error handling must be laid out, and behavior for invalid indices must be considered.