Generics and iteration type

Good morning all :slight_smile:.

I watched a talk about generics from wwdc-22 and decided to recreate the whole thing as an exercise and just when I was about to finish I got this error when passing a func directly to a forEach. (feedAll)

Here's the code:

import Foundation

protocol FarmAnimal {
    associatedtype Food: AnimalFood
    func eat(food: Food)
    var typeOfFood: Food.Type { get }
}

protocol AnimalFood {
    associatedtype CropType: AnimalCrop where CropType.Food == Self
    static func grow() -> CropType
}

protocol AnimalCrop {
    associatedtype Food: AnimalFood where Food.CropType == Self
    func harvest() -> Food
}

struct AnimalFarm {
    func feed(_ animal: some FarmAnimal) {
        let crop = animal.typeOfFood.grow()
        let food = crop.harvest()
        animal.eat(food: food)
    }

    func feedAll(_ animals: [any FarmAnimal]) {
        // OK
        for animal in animals {
            feed(animal)
        }

        // OK
        animals.forEach {
            feed($0)
        }
        
        // Type 'any FarmAnimal' cannot conform to 'FarmAnimal'
        animals.forEach(feed)
    }
}

struct CowAnimal: FarmAnimal {

    var typeOfFood: HayFood.Type { HayFood.self }

    func eat(food: HayFood) {
        print("eat some food: \(food)")
    }
}

struct HayFood: AnimalFood {
    static func grow() -> HayCrop {
        HayCrop()
    }
}

struct HayCrop: AnimalCrop {
    func harvest() -> HayFood {
        HayFood()
    }
}

Could someone explain what's the difference here?

Your feed function takes a generic FarmAnimal, while your feedAll function takes an array of existential FarmAnimals. By passing an existential to a generic function, Swift implicitly opens that existential for the scope of that function. The fact that this doesn’t work when passing the feed function into the forEach seems like bug to me. Check out the corresponding proposal for more details.

A bug was the first thing that came to mind but I thought that maybe I’m missing something here and forEach + closue + function call works a bit different than forEach + func as parameter

1 Like