I am encountering an error when trying to pass the result of an anonymous closure that produces a random boxed type conforming to the Shape protocol to a function expecting some Shape. Here is the code:
class Example {
func doSomething() {
let anyDrawable: any Shape = {
if Bool.random() {
return Circle()
} else {
return Rectangle()
}
}()
getShape(input: anyDrawable) // No error
getShape(input: {
if Bool.random() {
return Circle()
} else {
return Rectangle()
}
}() as any Shape) // Error: Type 'any Drawable' cannot conform to 'Drawable'
}
}
public protocol Shape {
associatedtype Content
func draw() -> String
}
public struct Circle: Shape {
public typealias Content = String
public func draw() -> String { "Drawing a Circle" }
public init() {}
}
public struct Rectangle: Shape {
public typealias Content = Int
public func draw() -> String { "Drawing a Rectangle" }
public init() {}
}
public func getShape(input: some Shape) {
_ = input.draw()
}
In the first case, where I store the result of the closure in a variable (anyDrawable) and pass it to the getShape(input:) method, everything works fine. However, when I try to directly pass the closure’s result (after casting it to any Shape) into the same method, I get the error:
Error: Type 'any Shape' cannot conform to 'Shape'
My understanding is that the getShape(input:) method uses the opaque type some Shape and implicitly unbox existential types and should be able to work with any type conforming to Shape. I am wondering if this is expected behavior, and if so, why the variable assignment works but passing the closure result directly does not.
Is there something I am missing about the interaction between any Shape, some Shape, implicit opening of boxed types by some Shape ?
I'm not sure I understand what closure you're talking about (is there some part of the problem that isn't shown in your snippet?), but from what you've written it sounds like you have something like:
getShape(input: inputProvider() as any Shape)
Am I understanding correctly?
If so, I think you're running into the suppression behavior for implicit existential opening where the immediate result of an as any P coercion cannot be opened to a parameter accepting some P.
Right, like @Jumhyn says, we adopted the explicit use of as any Shape inside the parentheses of a function call as the syntax for telling the compiler "I explicitly don't want to implicitly open the existential." So, the compiler doesn't let you do that because you've essentially asked it not to.
Occasionally, there are other reasons why you want to write as any Shape than to disable implicitly existential opening. And as you show here, you can do so separately before passing the value as a function argument.
I was experimenting with implicit existential opening and the explicit restriction of implicit existential opening, and it looks like an edge case usage. However, I continued to explore these concepts with the following code:
var anyShapes: [any Shape] = [Circle(), Rectangle()]
// (*) I suppose implicit opening of existential is used
func test() {
let shapeSubscript = anyShapes[0]
getShape(input: shapeSubscript)
getShape(input: anyShapes[0]) // (*)
let shapeFirst = anyShapes.first!
getShape(input: shapeFirst) // (*)
getShape(input: anyShapes.first!) // Compiler error ->
/*
- Type 'any Shape' cannot conform to 'Shape'
- Required by global function 'getShape(input:)' where 'some Shape' = 'any Shape'
*/
}
My question is: is it expected behavior to get a compiler error on this line: getShape(input: anyShapes.first!)?