Search code examples
swiftsubscript

How can I create a subscript with a getter that returns an optional, but a setter that returns a non-optional


I am writing a custom collection called TriangularArray<T>. It represents a structure like this:

     x
    x x
   x x x
  x x x x
 x x x x x

where each x is an element in the array. I can access the elements with a row number and an index number, both zero-based. For example, accessing (4, 2) of the following:

     a
    b c
   d e f
  g h i j
 k l m n o

will result in m (5th row, third value in that row).

I used a [[T]] as the backing array and I wrote a subscript like this:

subscript(_ row: Int, _ index: Int) -> T? {
    get {
        // innerArray is the [[T]] used for backing
        if row < 0 || row >= innerArray.count {
            return nil
        }
        if index < 0 || index > row {
            return nil
        }
        return innerArray[row][index]
    }

    set {
        if row < 0 || row >= innerArray.count {
            return
        }
        if index < 0 || index > row {
            return
        }
        innerArray[row][index] = newValue!
    }
}

The logic is that the subscript will return nil if you access a non-existent row and index, like (1, 3). However, by making the subscript return T?, the newValue in the setter also becomes optional, and I have to force unwrap it.

I really want compile-time checking of this kind of thing:

triangularArray[0, 0] = nil // this should be a compile time error

I tried looking this up on Google but I only found this question, which is very outdated. Surely we can do better in Swift 4.2, right?


Solution

  • It is unfortunately impossible to do with Swift (and other languages working with dynamic arrays) for the reason that the size of the array is dynamic and is not know at compile time (it can be initialized with any value when the program is running).
    For example, if the TriangularArray<T> is of size 1, then [0, 0] is a valid element, and the compiler cannot know that in advance.
    In the standard library, there's no compile-time error when you try to access an array like array[-1] - this will always compile but will always result in a runtime error.

    I think the solution that you currently have using optionals seems to be the best scenario here. You also be consistent with how Array works, and trigger a fatalError if invalid subscripts are given.
    An alternative would to create a custom Index struct within TriangularArray that represents the triangle's indexes, and can be optionally built from a x and y value (though this could complicate things quite a bit).

    PS: This answer assumes that TriangularArray<T> can have a triangle of an arbitrary height (a height that can be specified at runtime), as it's not specified in the question. If the height is defined at compile-time, then the bounds can be hardcoded and like @Raul Mantilla mentionned #error may be used.