3

The below code gives me an error. Is there a way to simply have this return nil?

var testArray: [Int]?

testArray = [1,2,3]

testArray?[9]

4 Answers 4

9

There are 3 possible things here that you might be conflating:

  1. An optional of an array – that is, a variable that might contain an array of integers: [Int]?
  2. An array of optionals – that is, an array that contains variables that might be integers: [Int?]
  3. Subscripts that return an optional because maybe that subscript isn't valid - that is, a subscript that might return an integer if the index is valid: subscript(index) -> Int?.

What you've got in your example is the first case – an optional of an array. That is, either testArray is nil, or it's a valid array containing zero or more elements.

To access anything inside an optional you have to first unwrap it. You can either force-unwrap it with ! (Try not to do this unless you have a really good reason – no not that reason. No not that one either). Or you can conditionally unwrap it, either using if let, or using one of the operators like ?, ?., ?? etc. In this case, testArray?[9] means, "if testArray is nil, then nil, otherwise {Some whatever is at position 9}. But there is no value at position 9 – this is only a 3-element array. So you get a runtime assertion.

(Another way of writing testArray?[9] would be testArray.map { $0[9] }, where inside the block the array is unwrapped and valid, but if it’s nil the block never gets executed. But the result is still that you try to access the 9th element of the array, and that's not allowed and you get a runtime error)

The second case, an array of optional integers, would mean that you could access testArray[1] and you'd get back {Some 2}, because the array holds optionals. But again, you can't access the 9th element, because there is no 9th element.

Finally the third case is where calling subscript gives you back an optional value, and if it's not a valid index, you get back a nil. This seems to be what you were expecting. Swift arrays do not do this. Swift dictionaries do, if you are looking up by key. If the key has a value, you get back {Some value}, if not, you get nil. The shorthand justification for this is that dictionaries contain sparse data, whereas arrays contain dense data.

If you're interested, I wrote posts about both the pro and anti argument for making arrays return optionals from subscript. But for now, like the other answers say, you have to check your bounds before accessing elements. You might want to try some of the helper methods like first, last, find, as well as map and filter etc rather than accessing elements directly, as this makes many of these problems go away.

(another interesting thing to think about – all 3 cases above can be combined together. So you could have an optional dictionary of optionals, that returned optionals when you accessed it. If you're still hazy on all this, try playing around with that in a playground :)

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks. In real-world- application, I was using optional chaining to perform method on array object. I though that if array index was out of bounds, chaining would produce nil. Sadly, it does not. I will read your post. I think the array.contains{} might work nicely with optional chaining, or custom method.
Something like this would allow that: extension Array { func safeIndex(i: Int) -> Element? { return i < self.count && i >= 0 ? self[i] : nil } }
I think the reason that arrays don't return nil for out of bounds error (and a full optional for in bounds) is simply because the array itself may contain nils - If I have an array of Int?, and call array[8] and it returns nil, was that because I made a request that was out of bounds, or because [8] is meant to be nil? There is ambiguity. Dictionaries on the other hand can't contain nil values. Thus if you get a nil return from the dictionary, you know it's because the key you looked up didn't exist.
@Jai no, this isn't the reason. You can still return an optional optional i.e. if a dictionary contains optional integers as its values, getting a value for a key would return Optional<Optional<Int>> (or Int?? for short). In cases where the value for a given key exists and is nil, a dictionary returns .Some(nil), which is very different to nil, which is what is returned if no value for that key exists. An optional-returning array subscript would work the same way.
@AirspeedVelocity Ah yup, you are 100% correct. Cheers for the explanation!
2

You need to do the range check explicitly, like this:

var testArray: [Int]?
testArray = [1,2,3]

let index1 = 9
let numberNine : Int? = index1 < testArray!.count ? testArray![ index1 ] : nil
println(numberNine)

let index2 = 2
let numberTwo : Int? = index2 < testArray!.count ? testArray![ index2 ] : nil
println(numberTwo!)

Conditional expression ... ? ... : ... will check the condition before deciding what side of the : to evaluate, so testArray![ index1 ] is protected by the condition: it will not cause an exception when the index is out of range.

4 Comments

The problem I see with this approach is that if you have testArray = [1,2,9] then things get messed up because it's based on the array count, and not the actual array key.
@l'L'l Where does the notion of 9 having to be a value in the array is coming from? I see two answers, both upvoted, that try to check for it, but the question title quite bluntly says that op is looking to check a value at a specific index. I do not see how one could misinterpret testArray?[9] as an attempt to find value 9 in the array.
The title mentions the index error, although the question doesn't say anything about checking for the value at the specific index. Maybe I misread the question perhaps?
BTW - I upvoted your answer, regardless if that's the (unclear) question being asked, since it's a good example of checking the index range. :)
1

If you want to know if the array contains a specific value you can use the global contains function:

var testArray: [Int]?
testArray = [1,2,3]

if let actualTestArray = testArray {
    let doesContain9 = contains(actualTestArray, 9)
    println(doesContain9)  // prints "false"
}

If you can make testArray non-optional you can just do

var testArray: [Int]
testArray = [1,2,3]
let doesContain9 = contains(testArray, 9)
println(doesContain9)  // prints "false"

Comments

0

When trying to access an index out of bound in Swift you should be thrown an error like EXC_BAD_INSTRUCTION at runtime.

Maybe you want to use an NSDictionary. In fact, with dictionary you will not have any runtime error when trying to access a key that doesn't exist. It will simply return nil

If you want to use array, you need to check the number of item you have in your array before accessing using the count property of your array

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.