Implicit existential opening doesn't seem to capture the type

Hi all,
I was expecting that the results of calling _openExistential and using implicit existential opening would be the same, but when I run the following:

func genericFunction<T>(_ v: T) {
    print("value: \(v)", "typeUsingTself: \(T.self) typeUsingTypeOf: \(type(of: v))" )
}
func doSomeWork(x: Any) {
    _openExistential(x, do: genericFunction)
    genericFunction(x)
}
doSomeWork(x: 0)
doSomeWork(x: 0.0)
doSomeWork(x: "zero")

I get:

value: 0 typeUsingTself: Int typeUsingTypeOf: Int
value: 0 typeUsingTself: Any typeUsingTypeOf: Any
value: 0.0 typeUsingTself: Double typeUsingTypeOf: Double
value: 0.0 typeUsingTself: Any typeUsingTypeOf: Any
value: zero typeUsingTself: String typeUsingTypeOf: String
value: zero typeUsingTself: Any typeUsingTypeOf: Any

Does anyone know why simply calling genericFunction(x) won't capture the type inside the function?

Your generic function has no constraints, so it won’t open any existentials. I think this was done to avoid changing existing code that already passed Any into unconstrained generic positions.

Is there anything else I could do to capture the type of Any and use the type in a generic context, besides using _openExistential function?

I don’t believe so as such, but what do you want to do with it that you can’t do with type(of: blah)?

I want to have the following:

protocol ProtocolA {
     associatedtype Value
}
struct StructA<Value>: ProtocolA {
     var value: Value
     // ...

}

and

func makeA(_ value: Any) -> any ProtocolA {
   // create an instance of StructA using an appropriate type 
   // (not just Any) and return it as any ProtocolA 
}

I can easily do that when Value is constrained by some protocol, but what if I wanted Value to be anything, without having to invent a special protocol to make this work?

I think you’ve found the one case that isn’t covered by the existing rules: wrapping a value in some kind of container. I don’t know of a workaround; maybe somebody more recently working on Swift does.

This certainly works

protocol ProtocolA {
    associatedtype Value
}
struct StructA<Value>: ProtocolA {
    var value: Value
}
func genericFunction<T>(_ v: T) -> any ProtocolA {
    StructA<T>(value: v)
}
func makeA(x: Any) -> any ProtocolA {
    _openExistential(x, do: genericFunction)
}
print(makeA(x: 0))
print(makeA(x: 0.0))
print(makeA(x: "zero"))

but my worry is that some platforms may not allow this due to the _openExistential usage.

1 Like

It doesn’t have anything to do with “capturing”—the static type really is Any, and that’s because genericFunction’s generic parameter has no constraints, so Any is just fine without having to open the existential to pass the argument to the function.

For backward compatibility with versions of Swift before implicitly opened existentials were implemented, this behavior is preserved for Swift 5 even in releases where the feature is now implemented. For the Swift 6 language mode, the existential will be opened as you’re expecting.

See the original proposal text for more explanation here.

4 Likes

Why do you need to pass Any, instead of …?

func makeA(x: some Any) -> any ProtocolA {
  genericFunction(x)
}
// or
func makeA<T>(x: T) -> any ProtocolA {
  genericFunction(x)
}

Because in my use cases, I want to be able to pass the result of
if let value = someStruct[keyPath: keyPath] where keyPath is stored as AnyKeyPath.

So

protocol ProtocolA {
    associatedtype Value
}
struct StructA<Value>: ProtocolA {
    var value: Value
}
func genericFunction<T>(_ v: T) -> any ProtocolA {
    StructA<T>(value: v)
}
func makeA(x: some Any) -> any ProtocolA {
    genericFunction(x)
}
struct AnotherStruct {
    var value: Double
}
let anotherStruct = AnotherStruct(value: 1.0)
let kp: AnyKeyPath = \AnotherStruct.value

if let result = anotherStruct[keyPath: kp] {
    print(makeA(x: result))
}

gives me StructA<Any>(value: 1.0), whereas

protocol ProtocolA {
    associatedtype Value
}
struct StructA<Value>: ProtocolA {
    var value: Value
}
func genericFunction<T>(_ v: T) -> any ProtocolA {
    StructA<T>(value: v)
}
func makeA(x: Any) -> any ProtocolA {
    _openExistential(x, do: genericFunction)
}
struct AnotherStruct {
    var value: Double
}
let anotherStruct = AnotherStruct(value: 1.0)
let kp: AnyKeyPath = \AnotherStruct.value
if let result = anotherStruct[keyPath: kp] {
    print(makeA(x: result))
}

produces correct output StructA<Double>(value: 1.0).