It's the intended behavior.
Type any StarField
is an existential type, which means it's a kind of "box" that can hold any concrete type that conforms to StarField
and Codable
and Equatable
.
The box itself, though, does not conform to StarField
or Codable
or Equatable
. That is for technical reasons, not an arbitrary decision.**
When you have a function parameter of some StarField
, the compiler generates code on your behalf to extract the value of concrete type from the box, and pass that value into runSome
. The some
keyword here means the concrete type is opaque — we can't "see" what concrete type the box contains — but that doesn't matter, because all possible concrete types are conforming.
OK, that's why runSome
works.
For runSome2
, something completely different is going wrong. You want to pass in a keypath, and the WritableKeyPath
type requires a base type (FieldContainer
in your example) which must be Equatable
.
But your FieldContainer
isn't Equatable
!
You would need to do at least this:
class FieldContainer: Equatable {
but that's not enough on its own. FieldContainer
has a property of type any StarField
which isn't Equatable
(see above), so the compiler can't synthesize Equatable
conformance for FieldContainer
for you.
To make this work, you'd have to provide your own implementation of Equatable
for your FieldContainer
class.
** Here's the reason why existential box types cannot in general conform to their underlying protocol:
Think about any Equatable
as a simplified example. If any Equatable
conformed to Equatable
, you could have one any Equatable
box containing an Int
, and another containing a String
, because both Int
and String
conform to Equatable
.
But you couldn't check whether those 2 boxes were ==
to each other, since there's no equality function to test types Int
and String
.