Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Extension for Array where Element is Optional

Tags:

swift

I have an array of String? like so

var myArray: [String?] = ["2", "banana", nil, "31"];

I have another array of String like so:

var myStringArray: [String] = ["2", "3"]

I wrote an extension for Array to return me an Optional if index is not valid (which avoids the whole app crashing), but I really only want it to return Optional if the Element is NOT optional, and just return Element if it is Optional

    func safeIndex(_ index: Int) -> Element? {
        if self.indices.contains(index) {
            return self[index]
        }
        
        return nil
    }

If i run this against myArray the resulting type is String??

let item = myArray.safeIndex(1) // item is String??
let strItem = myStringArray.safeIndex(99) // strItem is String?

How do i change my extension such that it gives me String? for BOTH cases

Note: If i am explicit about my types, I can get what I want like so

extension Array where Element == String? {
    func safeIndex(_ index: Int) -> String? {
        if self.indices.contains(index) {
            return self[index]
        }
        
        return nil
    }
}

However, this approach requires me to write a bunch of extensions for a bunch of types i might have in an array which is a massive meh.

like image 692
zaitsman Avatar asked Nov 30 '25 08:11

zaitsman


2 Answers

This is kind of tricky but you can create an AnyOptional protocol that requires an associatedtype (Wrapped) and a computed property to return the optional type. Then you can return the element unwrapped if the index is valid otherwise return nil.

protocol AnyOptional {
    associatedtype Wrapped
    var optional: Optional<Wrapped> { get }
}

extension Optional: AnyOptional {
    var optional: Optional<Wrapped> { self }
}

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

extension Collection  {
    subscript(safe index: Index) -> Element.Wrapped? where Element: AnyOptional {
        indices.contains(index) ? self[index].optional ?? nil : nil
    }
}

var myArray: [String?] = ["2", "banana", nil, "31"]
var myStringArray: [String] = ["2", "3"]

let item = myArray[safe: 1] // item is String?
let strItem = myStringArray[safe: 99] // strItem is String?
like image 70
Leo Dabus Avatar answered Dec 02 '25 04:12

Leo Dabus


I think what you want could be achieved with two extensions and a dummy protocol, which would constrain the array's Element. Something like that:

protocol OptionalType {}
extension Optional: OptionalType {}

extension Array where Element: OptionalType {
    func safeIndex(_ index: Int) -> Element {
        return self[index]
    }
}

extension Array {
    func safeIndex(_ index: Int) -> Element? {
        if self.indices.contains(index) {
            return self[index]
        }
                
        return nil
    }
}

Then this would work:

var myArray: [String?] = ["2", "banana", nil, "31"];
var myStringArray: [String] = ["2", "3"]

let item = myArray.safeIndex(1)
print(type(of: item)) // Optional<String>
let strItem = myStringArray.safeIndex(99)
print(type(of: strItem)) // Optional<String>
like image 21
kovpas Avatar answered Dec 02 '25 03:12

kovpas



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!