Search code examples
swiftswift-extensions

Cannot use Array initializer in an extension function for an array type in swift


Using Swift 5.7.2 and Xcode 14.2 I am attempting to write an extension function to an array of a certain type i.e. [MyClass]. Inside the function I would like to be able to use the Array() initializer to convert a set to an array, but I am not able to. I get the following error: No exact matches in call to initializer.

To simulate the problem I created a small playground with the code found below, where I simply try to extend [Int]. Additionally I realized that this is only a problem when extending an array, as the error does not appear when I extend just the Int type.

I am super curious about why this happens, and I hope someone can help me figure it out. There is most likely a logical explanation to this.

Extending [Int] (does not work)

extension [Int] {
    func foo() {
        let strSet = Set(["a", "b", "c", "a"])
        let strArray = Array(strSet)  // No exact matches in call to initializer
        print(strArray)
    }
    
    func bar() {
        let strSet = Set(["a", "b", "c", "a"])
        let strArray = strSet.map {$0}
        print(strArray)
    }
}

Extending Int (works fine)

extension Int {
    func foo() {
        let strSet = Set(["a", "b", "c", "a"])
        let strArray = Array(strSet) // Works fine
        print(strArray)
    }
    
    func bar() {
        let strSet = Set(["a", "b", "c", "a"])
        let strArray = strSet.map {$0}
        print(strArray)
    }
}

Not an extension (works fine)

func foo() {
    let strSet = Set(["a", "b", "c", "a"])
    let strArray = Array(strSet)
    print(strArray)
}

Solution

  • By looking at the more detailed error message:

    Swift.Array:3:23: note: candidate requires that the types 'Int' and 'String' be equivalent (requirement specified as 'Element' == 'S.Element')
        @inlinable public init<S>(_ s: S) where Element == S.Element, S : Sequence
                          ^
    
    Swift.RangeReplaceableCollection:3:23: note: candidate requires that the types 'Int' and 'String' be equivalent (requirement specified as 'Self.Element' == 'S.Element')
        @inlinable public init<S>(_ elements: S) where S : Sequence, Self.Element == S.Element
    

    It appears that Swift thinks you are trying to create a Array<Int>.

    If you just specify the generic type parameter, it will work as intended:

    let strArray = Array<String>(strSet)
    

    This is an instance of the issue SR-1789.

    Normally, when you are in a type declaration/extension of a generic type, you are allowed to use that type without generic arguments, and the type arguments will be inferred as the type parameters you declared. Like in these situations:

    extension [Int] {
        func foo() -> Array { // just writing "Array" works, no need to say Array<Int>
            fatalError()
        }
    }
    

    or,

    class Foo<T> {
        func foo() -> Foo { // just writing "Foo" works, no need to say Foo<T>
            fatalError() 
        }
    }
    

    However, this feature seems to be too "aggressive", in a sense.