Well, this is all predictable enough that the language and compiler ought to do it for you, in the fullness of time. You can wrap a more specific type-erasing container around the unconstrained existential by doing something like this:
protocol Foo {
associatedtype Bar
func foo(_: Bar) -> Bar
}
private extension Foo {
// Forward to the foo method in an existential-accessible
// way, asserting that the _Bar generic argument matches the
// actual Bar associated type of the dynamic value
func _fooThunk<_Bar>(_ bar: _Bar) -> _Bar {
assert(_Bar.self == Bar.self)
let result = foo(unsafeBitCast(bar, to: Bar.self))
return unsafeBitCast(result, to: _Bar.self)
}
}
struct AnyFoo<Bar>: Foo {
private var _value: Foo
init<F: Foo>(_ value: F) where F.Bar == Bar {
self._value = value
}
func foo(_ bar: Bar) -> Bar {
return self._value._fooThunk(bar)
}
}
The Bar == _Bar
assertion and unsafeBitCasts
should optimize away, and in the future when we support Foo<.Bar == T>
as a first-class existential type, it should have a compatible ABI with the unconstrained Foo
existential, as well as this struct wrapper, since one-field structs have the same ABI as their one field. Because of the need for unsafe type casts, though, this is probably code you'd want a code generator to produce for you instead of writing out by hand.
The "open existential" operation exists in SIL as a general-purpose operation; however, our optimizers and code generation are not necessarily set up to handle the operation in full generality outside of the fixed patterns we emit as part of method calls. There's also no surface-level language design for expressing a generalized open existential operations; @Douglas_Gregor had a strawman syntax sketched out in his generics manifesto, but I hope we can come up with something a little bit more polished in the end (though perfect is the eternal enemy of good.)
For the brave, there is in fact a general _openExistential
primitive hidden in the standard library you can play with, though I don't recommend using it in your code, since it may break or miscompile, and like all underscored things in the standard library, is subject to change without notice.