1

When accessing a dictionary, such as [String:Any], the result type is Optional(Any).

When indexing an array of [Any], the result type is Any, and the call can throw a fatal error.

Is there any reason for this difference?

It would be so nice to branch execution with a guard let, if let, ?, and ??, but instead you have to wrap array indexing in an if data.count <= index.

Rolf Locher
  • 163
  • 1
  • 13
  • If you know the size of the array then you know that all index values from 0 to size - 1 will return a value/object so making this optional would be weird in my opinion. A dictionary doesn’t have this well defined range so it’s not comparable – Joakim Danielson Jan 31 '20 at 18:55
  • 1
    *the call can throw an IndexError.*. No, it cannot `throw`, it raises an exception. An out-of-range exception cannot be caught with `try - catch`. – vadian Jan 31 '20 at 18:56
  • @vadian You're right. Some python snuck into the question there. Edited. – Rolf Locher Jan 31 '20 at 19:02
  • 1
    Most likely the reason is the Objective-C compatibility. – vadian Jan 31 '20 at 19:03
  • @JoakimDanielson that is actually a good point. There would be no other way to check if a key exists other than going for it. – Rolf Locher Jan 31 '20 at 19:04
  • 1
    Compare [Safe (bounds-checked) array lookup in Swift, through optional bindings?](https://stackoverflow.com/q/25329186/1187415). – Martin R Jan 31 '20 at 19:06
  • @MartinR Thanks for that link. My follow up question was definitely how to implement the prior. Is it possible to achieve the same functionality without the parameter name (e.g. array[safe: index] becomes array[index])? – Rolf Locher Jan 31 '20 at 19:11
  • 1
    @JoakimDanielson But it does have a "well defined set" (a range is just particular kind of set, with contiguous members), so I think that's a moot point. – Alexander Jan 31 '20 at 19:13
  • 1
    @RolfLocher: Theoretically yes: You can define a `subscript (index: Index) -> Element?` method, but then you have to call it as `let b: Int? = a[0]` or `let b = a[0] as Int?` so that the compiler can distinguish the methods from the context. I would not recommend that. – Actually I would not use a “safe subscript” at all. As said above, you always know which indices are valid. A failure might indicate a logic error in your code. – Martin R Jan 31 '20 at 19:16
  • @MartinR I did not consider that, thanks for the clarification. As an aside, surely accessing a dictionary with a nonexistent key would be just as much of a logic error, no? – Rolf Locher Jan 31 '20 at 19:24

1 Answers1

3

It's ultimately for performance reasons:

Commonly Rejected Changes

...

Strings, Characters, and Collection Types

  • Make Array<T> subscript access return T? or T! instead of T: The current array behavior is intentional, as it accurately reflects the fact that out-of-bounds array access is a logic error. Changing the current behavior would slow Array accesses to an unacceptable degree. This topic has come up multiple times before but is very unlikely to be accepted.

https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types

Though nothing stops you from rolling your own:

extension Collection {
    subscript(safelyIndex i: Index) -> Element? {
        get {
            guard self.indices.contains(i) else { return nil }
            return self[i]
        }
    }
}

let array = Array(0...10)
let n = array[safelyIndex: 3]
print(n as Any) // => Optional(3)
Alexander
  • 59,041
  • 12
  • 98
  • 151