Why can `any Shape` not conform to `Shape`

protocol Shape {
    func draw() -> String
}

struct FlippedShape<T: Shape>: Shape {
    var shape: T
    func draw() -> String {
        let lines = shape.draw().split(separator: "\n")
        return lines.reversed().joined(separator: "\n")
    }
}

func flip(_ shape: any Shape) -> some Shape {
    return FlippedShape(shape: shape) // Error here
}

This produces the error: "Type any Shape cannot conform to Shape", although I can't seem to understand why.

The Swift book in the Differences Between Opaque Types and Boxed Protocol Types section says (regarding a different scenario, view the link to get context):

The result of flipping a triangle is a value of type Shape , and the protoFlip(_:) function takes an argument of some type that conforms to the Shape protocol. However, a value of a boxed protocol type doesn’t conform to that protocol; the value returned by protoFlip(_:) doesn’t conform to Shape . This means code like protoFlip(protoFlip(smallTriangle)) that applies multiple transformations is invalid because the flipped shape isn’t a valid argument to protoFlip(_:).

Which corroborates that any SomeProtocol types do not conform to SomeProtocol, without explaining why, but it also says prior to that in the same section:

protoFlip(_:) [...] always returns a value of the same type. [...] protoFlip(_:) returns [aren't] required to always have the same type — it just has to conform to the Shape protocol.

It seems that the value returned by protoFlip(_:) does conform to the Shape protocol, but obviously this understanding is incorrect, does anyone know why any Shape does not conform to Shape?

Existential types don't have self-conformance, at least not yet. The topic was raised many times though (e.g. here).

You may want to take a look at Moving between any and some to fix your problem.
func flip(_ shape: some Shape) -> some Shape will work.

i found this quote from @xwu in a recent discussion regarding the motivation for why protocol existentials in general do not self-conform helpful:

i've also found this document regarding some of the differences between 'nominal' and 'non-nominal' types clarifying.

Consider what you're asking this function to do:

func flip(_ shape: any Shape) -> some Shape {
    return FlippedShape(shape: shape)
}

An opaque result type always has a single underlying type. At most, it can depend on the generic context of the function you get it from.

For example

func makeValue() -> some Equatable { 42 }

let valOne = makeValue()
let valTwo = makeValue()

valOne == valTwo // âś… Valid because we know valOne and valTwo have same type.
func makeValue<T>(_: T.Type) -> some Equatable { 42 }

let valOne = makeValue(Int.self)
let valTwo = makeValue(String.self)

valOne == valTwo // ❌ Invalid because makeValue<Int>() conceptually returns a different type to makeValue<String>()

let valThree = makeValue(Int.self)

valOne == valThree  // âś… Valid because we know valOne and valThree have same type.

In your case, the flip function is not generic - its parameter is an existential, potentially containing "any shape". And yet the result will have a different type depending on which value you call it with - perhaps a FlippedShape<Rectangle> one time, and FlippedShape<Circle the next.

That is why the result cannot be expressed as some Shape and must instead be any Shape.

That's really what it comes down to. Otherwise, the language does let you unbox the existential, and create a FlippedShape of it, but the result needs to be erased back to an any Shape.

3 Likes

In addition, the any in

func flip(_ shape: any Shape) -> some Shape {
    return FlippedShape(shape: shape) // Error here
}

makes no sense. There's no reason to use any in function inputs, it should be instead:

func flip(_ shape: some Shape) -> some Shape {
    return FlippedShape(shape: shape) // No error
}

It's semantically equivalent at the top-level of a parameter type, but of course there is still a distinction between

func f(_: [some P])

and

func f(_: [any P])

and so on.

1 Like

Of course, I meant plain any Protocol parameters, but any makes sense for heterogeneous collections like [any Protocol] and [String: any Protocol].

1 Like