Understanding the error "Type 'any Thing' cannot conform to 'Thing'"

I think I'm close to understanding something important about any boxes that would improve my reasoning about them, but it's still not quite making sense. I encountered the exact situation described in this topic, but the discussion moved on to a different way to solve the problem, where I'm still not sure why the error occurs. Here's a simplified version of my code that causes the error.

import SwiftUI

protocol Thing: Identifiable {
    var color: UIColor { get }
}

struct RedColoredThing: Thing {
    let color: UIColor = .red
    var id: UIColor { color }
}

struct BlueColoredThing: Thing {
    let color: UIColor = .blue
    var id: UIColor { color }
}

struct ContentView: View {
    @State private var things: [any Thing] = [
        RedColoredThing(),
        BlueColoredThing()
    ]

    var body: some View {
        VStack {
            ForEach(things) { // error: Type 'any Thing' cannot conform to 'Identifiable'
//            ForEach(things, id: \.color) { // No error, works as intended.
                Color(uiColor: $0.color)
            }
        }
    }
}

(I realize that using a generic rather than existential type would be preferable, but in the case I'm addressing the concrete type isn't known at compile time. I could possibly refactor so that it is known at compile time, but I'd like to understand this issue.)

Using the alternative ForEach initializer that takes an explicit ID argument works. That seems to imply that any Thing is a not a type that conforms to Identifiable, but is a type that has all of its properties.

To eliminate any confusion on my part about whether the problem is due to Thing's adoption of Identifiable, I introduced

func colors<Element: Thing>(of things: [Element]) -> [UIColor] {
    things.map(\.color)
}

and tried

ForEach(colors(of: things)) {

This results in the even more confounding (to me) error "Type 'any Thing' cannot conform to 'Thing'". My intuition about any Thing is that if there's anything (:drum:) it can do, it can to conform to Thing. Instead, it seems to have all of the properties of a Thing, but not the identity of it. Is this the correct way to think about any types? Is there a technical or fundamental reason why any Thing can't conform to Thing? I appreciate any help improving my mental model of this. Thank you.

See Hamish’s answer there:

Interesting. My mental model of any Thing is that it's a built-in implementation of Hamish's suggestion "Build a type eraser", i.e., that any Thing is a concrete type that can be instantiated as an entity on which all the properties and methods of a Thing can be accessed. Is that not right?

I think part of the problem is that, in your first code sample, Thing doesn't have any constrains on its ID type, so ForEach can't handle trying to iterate through [any Thing] where there's no guarantee that the IDs are the same type. In fact I'm surprised that the commented-out line works.

I'm also surprised that using UIColor as an ID works since AFAICT it's not Hashable.

Yeah, it's perhaps odd that I used UIColor as Identifiable's ID type, but I wanted a slightly more interesting visual in the sample app while still having the simplest code possible. :joy: UIColor is not the ID type used in the actual code I wrote when I encountered this problem.

UIColor inherits NSObject which conforms to Hashable.