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 Cocktail
s 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:
- Cannot convert return expression of type 'Array' to return type '[any ScrollIndexable]'
- 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
}
}