One of the key motivations for opaque types in return position was that library authors could hide the internal implementation details and instead only provide a constraint, so that library could evolve internally without breaking the API. I wonder if similar possible use case could exist for the function parameter position as well. Although maybe those cases can already be solved with some other way, like just using regular generics...
Iām pretty sure the compiler can already specialize the existential-taking function. (Hopefully someone with more expertise in the compiler can say whether bar
is guaranteed to be optimized equally well as foo
.)
The real difference is that with generics you can tell the compiler about same-type constraints:
func foo<T: Protocol>(_ p: T, _ q: T) {
// p and q are known to be the same type
}
func bar(_ p: Protocol, _ q: Protocol) {
// p and q could be of different types
}
You refer to the example with two Self parameters, the only constraint the callee (i.e. ==
) adds to the context is that both parameters must be the same which is currently only unique for the same variable or different aliases to the same variable.
But ==
doesn't specify what Self
has to be for a type, that is specified by the caller, in our case it was specified by another callee with name Private
which was executed in the caller.
First I wouldn't call a value an existential, this is more kinded to a variable.
Second, we could also store the proofs of some P
inside the value of some P
and unwrap the existential behind, but why we should do so if there is the option to optimize the boxing of some P
out by simply replacing the box with the underlying type.
Yes, I see your point.
The Swift definition of existentials is more related to implementation details, my is more of abstract nature. So you are mostly correct with your conclusion.
No, I'm using
any
to refer to protocol types as the Swift Programming Language Guide calls them. Generics and protocol types are identical on the call site:func foo<T: Protocol>(_ p: T) {} func bar(_ p: any Protocol) {}
you can pass any
Protocol
-conforming type to bothfoo
andbar
, but the difference lays in the possibility to specialize the former.
Is it really identical? Current behavior is not totally the same. Is the conformance also the feature that would be achieved in the future Swift? (I know it's true for Error
)
// Non-Existential Type (concrete type)
let value1: Struct= Struct()
// Existential Type (protocol as type)
let value2: Protocol = Struct()
let _ = foo(value1) // possible
let _ = foo(value2) // impossible, protocol 'Protocol' as a type cannot conform to the protocol itself
let _ = bar(value1) // possible
let _ = bar(value2) // possible
In some cases, this may not be possible, and a distinction must be made.
protocol Protocol {
static func bar() -> Int
}
struct Struct: Protocol {
static func bar() -> Int {
42
}
}
func foo<T: Protocol>(_ value: T) {
let value = T.bar()
}
// What happens if call `foo` with protocol type
// `Protocol` doesn't have any implementation
let value: Protocol = Struct()
foo(value)