Home > Blockchain >  Why can't Swift infer this return type properly?
Why can't Swift infer this return type properly?

Time:12-28

I'm trying to do something I think should be pretty simple, but I'm running into trouble with Swift's type inference. I really don't understand why it's falling down here.

I have a type Cocktail, which has other properties, but the only one important here is the name:

struct Cocktail {
    // ... other stuff
    let name: String
}

Then I have two protocols:

protocol ScrollIndexable {
    var scrollIndexTitle: String { get }
}

protocol ScrollIndexProviding {
    var scrollIndices: [any ScrollIndexable] { get }
}

along with a simple conformance on String to ScrollIndexable:

extension String: ScrollIndexable {
    var scrollIndexTitle: String { self }
}

I want to make it so that I can use an array of Cocktails as a ScrollIndexProviding:

extension Array: ScrollIndexProviding where Element == Cocktail {
    var scrollIndices: [any ScrollIndexable] {
        let firstCharacters = reduce(into: Set<String>()) { partialResult, cocktail in
            guard let firstCharacter = cocktail.name.first else {
                return
            }
            partialResult.insert(String(firstCharacter))
        }
        // The return line here has two errors:
        // Cannot convert return expression of type 'Array<Cocktail>' to return type '[any ScrollIndexable]'
        // No exact matches in call to initializer
        return Array(firstCharacters)
    }
}

This extension fails to build, with two errors:

  1. Cannot convert return expression of type 'Array' to return type '[any ScrollIndexable]'
  2. No exact matches in call to initializer

The second error seems like noise to me, since Set conforms to Sequence, so I should be able to use that init method.

The first error is confusing to me since the firstCharacters array is of type Set<String>, so the error message just doesn't seem to make any sense. Is there something I'm misunderstanding about the any keyword here? What's going on?

CodePudding user response:

The issue is that you're inside an extension of Array where the Element is Cocktail, so when you try to create an array without specifying the element type the compiler will assume you mean for the element type to be Cocktail.

extension Array where Element: Cocktail {
    func someMethod() {

        // This array is of type `Array<Cocktail>` since the compiler 
        // assumes the array's element type should be the same as 
        // Self's element type, which (from the extension) is `Cocktail`.
        let array = Array()
    }
}

So, to fix this, just explicitly tell the compiler that the array's element type is String, as in:

extension Array: ScrollIndexProviding where Element == Cocktail {
    var scrollIndices: [any ScrollIndexable] {
        let firstCharacters = reduce(into: Set<String>()) { partialResult, cocktail in
            guard let firstCharacter = cocktail.name.first else {
                return
            }
            partialResult.insert(String(firstCharacter))
        }
        
        return Array<String>(firstCharacters)
        //          ^^^^^^^^ add this
    }
}
  • Related