Require the protocol to be a class type but the protocol is constrained to AnyObject

protocol P: AnyObject {}

class S: P {}

struct Gen<T: AnyObject> {
    var value: T
    init(value: T) {
        self.value = value
    }
}

func f<T: AnyObject>(_ v: T) {
    print("T conforms to P")
}

let s: any P = S()
f(s) // ok 

let g = Gen(value: s) // error: Generic struct 'Gen' requires that 'any P' be a class type

but P is constrained to AnyObject . Passing into the function has no issue. but passing to a generic type, it got an error. I don’t get it.

The following explains the behavior you’re seeing:

  • Only class types can conform to an AnyObject constrained protocol. An instance of a class type is a single retainable pointer.
  • An any P itself is not a class type, and its representation is not a single retainable pointer. So any P does not satisfy an AnyObject requirement.
  • However, there is an implicit conversion from an any P to an AnyObject.
  • You can also “open” an any P and use the concrete type of the payload as the generic argument of a generic function.

it's not exactly the same as your question, but you may find this related thread of interest, as it covers similar ground: My frustration with Generics and Protocols.

p.s. and, if you want more consistency, you can compile with vanilla -swift-version 5 mode, and it will error in both cases :slight_smile:

Since this comes up quite often, perhaps once more a bit more explanation:

any P is its own type. It is literally “a type that can store any other type as long as that stored type conforms to P". Note that this sentence does not imply any other relationship with P, most importantly it does not say “and that conforms to P itself as well”.


Okay, so while that does explain the error message, why does it matter?

Well, the short version is that since unlike in e.g. Java, in Swift variables need to "know” how large the type that’s stored in them is. In Java, everything (except base types) is basically just a pointer, so under the hood variables are pretty much all the same, storage-wise (I am simplifying here). In Swift, objects are not necessarily class instances (your Gen type isn’t, for example), so a variable that “holds” them needs to be able to “contain” them. This needs to be determined during compile time. For Gen this means any variable that stores a value of this type needs to know which concrete T is involved, otherwise the compiler doesn’t know how large the variable needs to be.

Existentials (that’s basically all types that are prefixed by any) offer some alleviation for this. A variable typed with this is basically a “box” that, during run time, can hold any type that conforms to a given protocol, as formulated above. This means the compiler does not know how large any variables you would need to hold the object of the concrete type “in” that box are. You use one to store a value of S in the s variable.

So how can you get to the “inside” then? Your function f is not the best example of what’s happening as it doesn’t do anything related to P or Gen, but lets check what happens here: Generic functions are a (the) way to “open" existentials. They “magically” turn the parameter (in this case v) that’s typed with the generic placeholder (in this case T) into the concrete type from inside the box. They can safely do that by enforcing that within their scope the parameter is only used according to the protocol that constrained the generic (in this case AnyObject). That scope thing is important! In f you’re not actually doing anything, and AnyObject doesn’t really provide much functionality, but that’s beside the point of the mechanic. You can pass s to f as this P extends AnyObject. So when the compiler sees this, it interprets this syntax as “don’t really pass s as any P, unbox it and pass its contents as value concretely typed AnyObject.

So, why does this not work for the initializer of Gen? Well, in this case this is easier explained by using an equivalent “builder” function:

func makeGen<T: P>(_ v: T) -> Gen<T> {
    return Gen(value: v)
}

let g = makeGen(s) // error

This won’t work because of the return type. You’re “leaving the scope” of the function, but try to influence where the returned value is stored after it has been declared. Inside the function vis properly typed as whatever concrete type was “in the box”. [1] f worked in the same way. But: You then try to pass back out this information via the concretely typed return value. But what that would be can only be defined during runtime. When this is compiled, we simply don’t know that yet! Swift has no syntax to express this at the moment (and I doubt such a thing would be added). For non generic return types you can of course once more use any Whatever, but you cannot express “a Gen that contains any P” or something like it.


Sorry for the lengthy explanation, but I thought a more in-depth analysis might help other stumbling on this thread, too.


  1. You can imagine there being an infinite number of implementations of the function for each possible type that conforms to Pand the one that matches S is chosen during runtime (that’s a poor-man’s mental model of how generics work, the truth is more clever, obviously). ↩︎

10 Likes