1

To prevent potential crashes, whenever I attempt to access a specific element in an array I normally do some safety-checks. I just realised I'm kinda tired of doing that. Is there a way to do it quickly and safely?

var array:[String] = [/*Filled with stuff*/]
func someFunc(someIndex:Int){

    //This is unsafe. It will crash if index is negative or larger than count.
    let text = array[someIndex]

    //This is completely safe, but boring.
    let text:String
    if someIndex >= 0 && someIndex < array.count{
        text = array[someIndex] 
    }else { return }

    //What I want is something like this:
    let text = array.hasElement(at: someIndex) ? array[someIndex] : return
    //I know, I can't really put a 'return' in a statement like this, but I really want to..

    //Optimally, I'd like something like this:
    guard let text = array[someIndex] else { return }
    //But this is illegal because it's not an conditional binding with an optional type.
}

While writing this I realised I could probably create my own extension of Sequence or something, to add a function for func hasElement(at:Int)->Bool. But I'd rather want a way to use guard since I can't do an inline condition ? true : return..

Any one-liners are appreciated..

Sti
  • 8,275
  • 9
  • 62
  • 124
  • 1
    Various solutions at https://stackoverflow.com/questions/25329186/safe-bounds-checked-array-lookup-in-swift-through-optional-bindings and https://stackoverflow.com/questions/37222811/how-do-i-catch-index-out-of-range-in-swift – Martin R Apr 25 '18 at 19:59
  • 1
    @RAJAMOHAN-S What if `YourINDEX` is `-3`? A negative number will crash just as much as if index is higher than the number of elements. I already compensated for this in the question. Also, getting a `boolValue` is not at all what I was looking for. I was looking for a *better* way than to *check* this every time. – Sti Apr 25 '18 at 20:14
  • @Sti, great :) Since, array's doesn't have negative indices! So, I thought you are using positive `Int`'s only.! lol – Rajamohan S Apr 25 '18 at 20:22

2 Answers2

7

Simple enough to do with an extension on Collection:

extension Collection {
    subscript(checked index: Index) -> Element? {
        if self.indices.contains(index) {
            return self[index]
        } else {
            return nil
        }
    }
}

And so:

let arr = ["Foo", "Bar", "Baz"]

print(arr[checked: 2]) // Optional("Baz")
print(arr[checked: 3]) // nil

If that's too much, you could just use the ternary operator:

let foo: String? = arr.indices.contains(3) ? arr[3] : nil
Charles Srstka
  • 16,665
  • 3
  • 34
  • 60
  • That's essentially https://stackoverflow.com/a/37225027/1187415 or https://stackoverflow.com/a/30593673/1187415 :) – Martin R Apr 25 '18 at 20:04
  • Well this is almost exactly what I wanted! I completely forgot subscript was a thing, thanks! All we need now is the ability to perform a return-command in the ternary operator (like Kotlin can do with their elvis-operator, as far as I understand) – Sti Apr 25 '18 at 20:18
  • @Sti Well, you can kind of, sort of do that, although it's not pretty: `guard let foo: String? = { bar ? baz : nil }() else { /* bail */ }` – Charles Srstka Apr 25 '18 at 20:22
  • @Sti Or, if your function throws: `let foo = bar ? baz : try { throw someError }()` – Charles Srstka Apr 25 '18 at 20:24
0

Simplest way of doing above is.

extension Array {
    func haveItem(at index: Index) -> Bool {
         return index < self.count
    }
}

if yourArray.haveItem(at: 4) {
     //You have some item at index 4 in yourArray object.
}
Syed Qamar Abbas
  • 3,637
  • 1
  • 28
  • 52