27

I feel like array[index] should return an optional, given that the index could be out of bounds.

This would allow for code such as:

if let object = array[index] {
  // Do stuff
}

This is easy to do with an extension method, but knowing the real reason why this is the case would be good to know.

5 Answers5

33

This was one of my first Swift radars, which was closed as "Behaves Correctly." It was also discussed in the dev forums. As noted by Dave Abrahams:

As to rationale, It’s easy and very common to have static knowledge that the index is in range.... In this case it’s much better to establish a precondition that the index is valid so that common use-cases don’t have to cope syntactically with a failure that can’t happen. Contrast this with dictionary indexing on keys, where it’s commonly not known whether the key is already in the dictionary.

As I've grown more experienced in Swift, I've come to agree. I do sometimes wish there were a "safe subscript" (like Mike Ash's) built-in, but I've come to agree that it shouldn't be the default.

Making it the default would make Arrays very difficult to work with, not just because of the required unwrap, but because the Index type would no longer be Int. It is required that subscript(Index) return Element (not Element?). This is why the Index of Dictionary isn't Key; it's DictionaryIndex<Key,Value>. Creating a special ArrayIndex would likely have a lot of annoying side-effects. (Maybe it would all work out in the end, but it's questionable whether it's worth it.)

The real lesson here is that you should avoid arbitrarily subscripting Arrays anyway. When practical, you should prefer to use it as a CollectionType. That means subscripting only with indexes that you fetched (with indexOf or indices for instance), and strongly favoring iteration (for-in, map) rather than subscripting. Use xs.first rather than xs[0]. If you treat it as a collection rather than an array, then you get the safety you're describing, while still leaving subscripts available when you need to solve special problems where you know the subscript is in range.

Here's an illustrative example. Consider this common loop, which you may think requires subscripting:

let xs = [1,2,3]

for i in 0..<xs.count {
    print("\(i): \(xs[i])")
}

We can do this a little better and not rely on our special knowledge of array indexing and make it work for all Collections:

for i in xs.indices {
    print("\(i): \(xs[i])")
}

But even that isn't necessary. We can do much better and make it work for all Sequences:

for (i, x) in xs.enumerate() {
    print("\(i): \(x)")
}

No subscript required.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • I would like to ask you to solve my trouble which I described in http://stackoverflow.com/questions/34321070/how-to-get-random-element-from-collectiontype Please, if you will have a time to check my question, it can help me (maybe other guys too) to better understand the concept of Swift language. I didn't received yet any answer, or recommendation ... – user3441734 Dec 17 '15 at 17:31
  • I really don't get why, as you said, _the Index type would no longer be `Int`_. By returning optional, the method signature would just become `public subscript(position: Self.Index) -> Self.Iterator.Element?`, wouldn't it ? Why would the parameter type change? – Martin Jul 11 '17 at 10:02
  • To be a `Collection`, you must implement `subscript(position: Self.Index) -> Self.Iterator.Element`. If it returns `Element?`, then that doesn't implement the required method and you're not a `Collection` any more. This is why the index of `Dictionary` cannot be `Key` (and isn't; it's `DictionaryIndex`). You can't just implement any subscript you want; you have to implement the subscript required by the protocol. You're free to *also* implement the `Element?` subscript taking `Int`, but `Int` wouldn't be able to be the `Index`. – Rob Napier Jul 11 '17 at 13:34
3

Try to look at this example:

var arr: Array<Int?> = []
arr.append(1)
arr.append(nil)
arr.count == 2
let n = arr[1] // nil

I think that with this example is easy to understand the reason. Even though your index is valid, you still receive nil.

So, we know the type, but:

if let n = arr[1] {
    // how to know whether the index is out of range
    // or a valid nil value was returned?
}
Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
user3441734
  • 16,722
  • 2
  • 40
  • 59
  • Invalid. The subscript operator could still return `Optional>` aka `Int??` in this case. – Tamás Zahola Dec 17 '15 at 13:42
  • The question is about to check for an invalid *index* rather than an invalid *value* – vadian Dec 17 '15 at 13:47
  • 2
    @vadian, if i don't know the the type of the array, and arr[1] give me nil, there will be two possible reasons, why i receive nil. out of index range, or nil (but still valid) value – user3441734 Dec 17 '15 at 13:50
  • @TamásZahola but the *value* would still be a `nil`, right? Try this example out in a playground: `if let n = arr[1] { ... }` won't enter closure, and neither will `if let n = Optional(arr[1]) { ... }`. Their types would be different, but their values, in both examples, `nil`. I think this answer makes a good point, cause a clause like `if let n = arr[1]` will not enter here, but if Swift allowed for e.g. `arr[100]` (here) to return `nil`, perhaps users would mistake the former for a case of the latter. I think this answer is within context of the question, although not very exhaustive. – dfrib Dec 17 '15 at 13:51
  • @user3441734 out of range will raise an exception. – vadian Dec 17 '15 at 13:52
  • @vadian: but the question asks if there is a reason why we get out of range rather than an optional! So this shows one additional reason why this would not be a good idea. The question does not ask about how things will behave, but rather, *why* they have chosen (in swift), to behave like they do. – dfrib Dec 17 '15 at 13:54
  • The question is whether it's possible to get an optional instead of an exception if the index is out of bounds. – vadian Dec 17 '15 at 13:56
  • 1
    @vadian yes, exactly. in the case you receive nil, you don't know the reason. is the index invalid, or the value is nil? – user3441734 Dec 17 '15 at 13:58
  • @vadian: since you now removed your answer, if you are one of the down-voters on this question, perhaps it's appropriate to remove that down-vote (based on the fact that you (possibly) misunderstood what the poster was asking?). I think this answer has a valid point. – dfrib Dec 17 '15 at 14:01
  • @dfri don't take care about, i am not vote eater. for me is satisfaction, that somebody else can see what is my point of view. thanks for you support in this discussion! – user3441734 Dec 17 '15 at 14:03
  • Once again, you'll never get `nil` if the index is invalid. The app will just crash. From `objectAtIndex` : *If index is beyond the end of the array (that is, if index is greater than or equal to the value returned by count), an NSRangeException is raised* – vadian Dec 17 '15 at 14:04
  • @vadian where my example crash? please, try it first by yourself. thank you in advance – user3441734 Dec 17 '15 at 14:06
  • at user3441734 you're welcome, it's just that I think this answer should be at status "0" at least. I, myself, rarely look at down voted answers. @vadian: but again, this question (and answer) is not regarding the behaviour of trying to access invalid indexes, but regarding *why swift chose not to treat such cases as optionals*. Rob's answer is exhaustive as to *the probable reasons for this*, but this answer adds another valid point to the discussion. The answer, however, could be formulated better, say with an `if let` clause, to better illustrate the discussion it holds. – dfrib Dec 17 '15 at 14:07
  • @user3441734 I refer to your statement *in the case you receive nil, you don't know the reason*. You do know because the index couldn't be invalid. – vadian Dec 17 '15 at 14:12
  • @vadian in the very first in the question is written : "I feel like array[index] should return an optional given that the index could be out of bounds". so i tried to explain, why (by my own meaning) this is not a good idea. if it comes to be true, in that case i see much bigger trouble to write my code properly. – user3441734 Dec 17 '15 at 14:18
  • @vadian it is OK, that you don't agree with me. negative reaction doesn't means nothing wrong at all. i am happy with all reactions, because they can give me a chance to think about. – user3441734 Dec 17 '15 at 14:21
  • @user3441734 I think it was a valid point, and added an answer to specify what I think you wanted to tell us. – dfrib Dec 17 '15 at 14:47
2

Given the exhaustive discussion under user3441734:s answer (which we should probably have taken in chat...), I feel I must make clear the somewhat OK point that I think user3441734 was trying to make.

First of all, note that the question covers

  • What is the reason for Swift to let a an invalid-index situation behave just like that (throwing exception, out of bounds), instead of returning an optional?

Rob's answer is exhaustive and cover this very well, but I do think that user3441734:s answer at least deserve a status >= 0, as it does contain another reason as to why the hypothetical case "swift lets invalid-index return an optional" might not be a good idea. Note that this is by no means " ... the real reason why ... " swift don't let invalid-index return nil, but a point I think deserves to be >= 0-voted.

The question does not cover:

  • Whether it's possible to get an optional instead of an exception if the index is out of bounds.

The question author himself states the "This is easy to do with an extension method".


So, with this out of the air, lets have a look at user3441734:s answer, and try to make it a little bit more clear what he/she tried to point out to us.

We will analyse the following expression under the assumption that we are in a parallel Swift universe, where invalid-index situations (w.r.t. arrays) are treated as optionals and return nil.

// Lets assume the following is all in the scope of some function
var arr: Array<Int?> = []
arr.append(1)
arr.append(nil)
arr.append(3)
print("\(arr)") // Optional(1), nil, Optional(3)

// lets say the function randomIntegerTwoOrFour() -> Int returns, 
// randomly, either value 2 or value 4.
let ourMagicAndAtCompileTimeUnknownIndex = randomIntegerTwoOrFour()

// now, in our parallel Swift universe, say we want to
// use an if let clause on our array at our magic index
if let n = arr[ourMagicAndAtCompileTimeUnknownIndex] {
    // ...
}
else {
    // lets say we're not careful here, and think that we've
    // entered this clause because of a nil valued member of
    // our array. If we try to use our magic index for non-nil
    // assignment, how would parallel universe Swift 2 handle this?
    
    // We could of course try some downcasting to infer actual, 
    // (optional) type ourselves, but this is not very "Swifty" 
    // w.r.t. if let clauses.
}

// on the other hand, say we want to guard against invalid-index
// in our array, using a guard let clause 
guard let n = arr[ourMagicAndAtCompileTimeUnknownIndex] else {
    print("Out of bounds!") 
         // or are we ...? Careful parallel Swift universe programmer!
    
    // Naturally, we could (and should, in parallel Swift 2 universe), 
    // look closer at what type n is at this point, but this is also
    // not very "Swifty" in this context).

    return
}

To wrap it up, this is the point that I inferred from user3441734:s answer, and I think it deserve no less, and perhaps no more, than a score of 0, however, not to be down voted.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • do you expect, that in the case of index out of range the returning type will be different? this seems strange for me too. can i receive in that case Index? or Elemen!? this is almost impossible in Swift. So i think, there is no chance 'look closer what type n is'. I thank you again, that you see my point of view! – user3441734 Dec 17 '15 at 15:13
  • The type for `n` above (wrapped as optional) would differ for the two, but not the value (`nil`). What you could possibly do is try different downcasts for `n` (`if let ... as? SomeType`), and depending on which downcast is successful, you could infer what the type hidden behind the optional is. Haven't tried this, though, but as you mention, if not impossible, at least impractical an "non-swifty". You're welcome, it was your point to begin with, not mine! – dfrib Dec 17 '15 at 15:18
  • how to continue by chat? – user3441734 Dec 17 '15 at 15:24
  • Usually you get prompted automatically after a few comments to continue in chat. An alternative is to manually open a chat room and link to it in the comments, to the user you want to continue discussing the subject with (see e.g. http://meta.stackexchange.com/questions/106467/how-can-i-move-a-discussion-to-chat-before-being-prompted#comment273454_106467 ). I'm off for now anyway, think this subject is quite exhausted by now. Take care and happy coding! – dfrib Dec 17 '15 at 15:31
  • nowadays we have subscript(index: Int) -> Element . what do you suggest as returning type in 'parallel swift word'? subscript(index: Int) -> Element? or some specialised subscript(index: Int) -> ReturnType? or what? the type of if let n = .... will still be the same in both cases. – user3441734 Dec 17 '15 at 15:35
  • hehe I'd have to think about that, but for now, I'll focus on our world's Swift for a few days :) might come back to this at a later time! – dfrib Dec 17 '15 at 20:16
  • What's "w.r.t."? – Iulian Onofrei Jan 21 '21 at 13:42
  • @IulianOnofrei with regard to – dfrib Jan 21 '21 at 17:34
1

It is probably by design. Since it's pretty easy to get number of items in array, if you'll iterate from 0 to count-1, there will always be a vaule. If array[index] could return nil, you'd have to unwrap an optional every time you iterated through an array, which would be pain in the ass :).

Adam Bardon
  • 3,829
  • 7
  • 38
  • 73
-1

It depends on the type of your array elements. For e.g if you define your array as

var arr:[String?] = Array<String?>(count: 5, repeatedValue: "SomeString")

Then array[index] will be an optional.

But inherently array[index] is a non-optional value because accessing an array outside its bounds itself throws an exception let alone getting its value. So you wont go past reading the element itself.

Pradeep K
  • 3,671
  • 1
  • 11
  • 15
  • The OP is asking about why accessing an index out of bounds traps instead of having the subscript return nil for indexes out of bounds. – Jon Hull Aug 20 '18 at 02:10