Implicitly opening existential with a generic struct initializer

In SE-0352, implicitly opened existentials when calling generic functions was added to the language. I'm hoping that generic struct initializers can be used as a generic function in this manner. I don't see what would prevent that from working. I'm finding mixed results, and I'm looking for some help to solve a problem.

The problem: I'm using SwiftUI to build a View hierarchy. In the parent view, I have a value of type any InsettableShape. I'd like to initialize a generic child view which unwraps that existential, so that it can work with some InsettableShape.

I've simplified the problem to some example code, which doesn't depend on the real SwiftUI, and even removes complexity like protocols with associated type or self requirements from the picture. I'm running into a Type 'any InsettableShape' cannot conform to 'InsettableShape' error.

// Simplified stubs to produce a minimal test case
protocol View {
    var body: Any { get }
}
protocol InsettableShape {
    var someMember: Any { get }
}

// Child view, pretty close to the real use case
struct MySymbolView<S: InsettableShape>: View {
    private let shape: S

    init(shape: S) {
        self.shape = shape
    }

    var body: Any {
        return shape
    }
}

// Somewhere inside the parent view, just a test
func test(testShape: any InsettableShape) {
    let result1 = MySymbolView(shape: testShape)
   // error: type 'any InsettableShape' cannot conform to 'InsettableShape'
}

As I've spent time reading the proposal linked above and experimenting with code, I've also found that if I simplify MySymbolView to only use a generic initializer, and not be a generic struct as a whole, then the compiler is happy. So it seems that initializers can in fact take advantage of SE-0352.

struct MySymbolView: View {

    init(shape: some InsettableShape) {
        // self.shape = shape
    }

    var body: Any {
        return true
    }
}

However, this isn't very useful to me, since I cannot actually build the child view I'm hoping to build without storing the value, and I cannot do that without specifying the type, which needs the generic placeholder outside the initializer.

Questions: Is there a better way to use a value of an existential type and unwrap it to produce a child view? In addition, can someone help me understand what exactly prevents the compiler from unwrapping and initializing successfully in the example above? I couldn't find anything in the proposal that mentioned why this case would be limited if the initializer on its own is fine.
Side note: I know that the iOS17 SDK introduced the AnyShape type, but I'm targeting a previous version of iOS. If the best solution possible here is to introduce my own AnyShape struct, I'm honestly not sure where to start on that, and would appreciate any guidance.

Thanks!

1 Like

We allow binding an opened existential to a generic parameter only if the generic parameter appears in certain positions: https://github.com/apple/swift-evolution/blob/main/proposals/0352-implicit-open-existentials.md#when-can-we-open-an-existential

A constructor inside struct G<T> is like a generic function returning G<T>. You will notice in the below, we accept the first but not the second:

struct G<T: P> {}

func f1<T: P>(_: T) -> T {}
func f2<T: P>(_: T) -> G<T> {}

let x: any P = ...

f1(x) // okay
f2(x) // error

So this means you cannot bind an opened existential to the generic parameter of a nominal type when calling the constructor, because the generic parameter always appears in the return type of the constructor in an unsupported position.

3 Likes

I asked a similar question a while back and got some replies from @Slava_Pestov that might be enlightening.

EDIT: (looks like the legend himself just barely beat me to the punch :joy:)

2 Likes