[SomeProtocol] with associated type works as [Any] - Why?

Xcode 13.4.1, Swift 5.6.1

In a nutshell: Swift complains when protocol SomeProtocol has associate type requirements and you attempt to type a variable or array as SomeProtocol.

Code
enum SomeEnum: String {
    case dummy
}

protocol SomeProtocol {
    associatedtype ParentKeys: RawRepresentable
    var children: [SomeProtocol] { get set }
}

struct SomeStruct: SomeProtocol {
    typealias ParentKeys = SomeEnum
    var children: [SomeProtocol] = []
}

let ss = SomeStruct(children: [SomeStruct()])

let c = ss.children
print(c)

However, using [Any] in place of [SomeProtocol] and then mapping the array to SomeProtocol works.

Code
enum SomeEnum: String {
    case dummy
}

protocol SomeProtocol {
    associatedtype ParentKeys: RawRepresentable
    var children: [Any] { get set }
}

struct SomeStruct: SomeProtocol {
    typealias ParentKeys = SomeEnum
    var children: [Any] = []
}

let ss = SomeStruct(children: [SomeStruct()])

any-array

This succinctly demonstrates how strange this seems. The typing system knows the mapped array is [SomeProtocol] but refuses to allow using that exact type explicitly.

Does anyone know why this is possible, or what's going on under the hood that may not be surfacing here as to why it works?

Previous to Swift 5.7 protocols came in two flavors. Those that didn't have associated (or Self) requirements that could be use for so-called existential types (like a base-class) or for a generic constraint and those that did have associated (or Self) requirements and could only be used for a generic constraint. You are bumping into that limitation.

Good news is that Swift 5.7 addresses some of this limitation. In Xcode beta, this compiles fine:

enum SomeEnum: String {
  case dummy
}

protocol SomeProtocol {
  associatedtype ParentKeys: RawRepresentable
  var children: [any SomeProtocol] { get set}
}

struct SomeStruct: SomeProtocol {
  typealias ParentKeys = SomeEnum
  var children: [any SomeProtocol] = []
}

let ss = SomeStruct(children: [SomeStruct()])

Notice the extra "any" in front of SomeProtocol. That makes it clear that you are using a boxed (type erased) type. Highly recommend watching the two generics talks this year from WWDC to get more info about what you can do now with Swift protocols and generics.

1 Like

Thanks for the tip @Ray_Fix.

Funny enough, I had tried [any SomeProtocol] but because I was compiling for Swift 5.6.1 (Xcode 13.4.1) it still proffered the same error so I didn't pursue any any further.

Voila:

swift-5.7

any really has nothing to with this behavior (I've seen this misconception all over the place, it really seems like this feature has mislead a lot of people). It's simply the new syntax for existentials. Rather, the compiler has been enhanced to better understand protocols with associated types when used as an existential, whether you use Protocol or any Protocol. It's the compiler version that matters.

2 Likes

Swift 5.7 requires the any keyword for existentials with associated types. It's only to avoid writing new code that will become invalid in Swift 6, so you're right that the syntax only artificially unlocks the capability.

4 Likes

Ah. I can see why people are confused then.

1 Like