In the document, the Chapter "Opaque Types" describes the difference between protocol types and opaque types as function return types as follows:
Another problem with this approach is that the shape transformations don’t nest. 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 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(_:).
However, in practice, unlikely, it is valid as proven by the following example:
#if swift(>=5.8)
print("hello swift 5.8+")
#endif
protocol Shape {
func draw() -> String
}
struct Triangle: Shape {
var size: Int
func draw() -> String {
var result: [String] = []
for length in 1...size {
result.append(String(repeating: "*", count: length))
}
return result.joined(separator: "\n")
}
}
struct FlippedShape<T: Shape>: Shape {
var shape: T
func draw() -> String {
if shape is Square {
return shape.draw()
}
let lines = shape.draw().split(separator: "\n")
return lines.reversed().joined(separator: "\n")
}
}
struct Square: Shape {
var size: Int
func draw() -> String {
let line = String(repeating: "*", count: size)
let result = Array<String>(repeating: line, count: size)
return result.joined(separator: "\n")
}
}
func flip<T: Shape>(_ shape: T) -> some Shape {
return FlippedShape(shape: shape)
}
func protoFlip<T: Shape>(_ shape: T) -> Shape {
if shape is Square {
return shape
}
return FlippedShape(shape: shape)
}
let smallTriangle = Triangle(size: 3)
let nestedThing = protoFlip(protoFlip(smallTriangle))
print(nestedThing.draw())
// hello swift 5.8+
// *
// **
// ***
What do you think about it?
Can it be a bug of the compiler?
Originally, it doesn't make sense that "However, a value of a protocol type doesn’t conform to that protocol."
Thanks!
I believe the description is outdated. Since introduction of SE-0352, any P can be passed as an argument of generic function, and the pointed behavior seems to be due to that change.
3 Likes
Yeah, seems outdated / overlooked. With the small mention that there are situations when the existential can't be opened, in which case you can nest flip but not protoFlip, e.g.:
func flip<T: Shape>(_ shapes: [T]) -> [some Shape]
...
func protoFlip<T: Shape>(_ shapes: [T]) -> [Shape]
...
// OK, compiles
flip(flip([smallTriangle]))
// Not OK: Type 'any Shape' cannot conform to 'Shape'
protoFlip(protoFlip([smallTriangle]))
I'm not sure if the documentation implies the latter case, but it worths mentioning.
2 Likes