init<each S>(_ somethings: repeat each S) { /*...*/ }
be able to count as implementing init()? (This is like when we allowed enum type cases to pose for a type-level method requirement.) Is this a bug? Or is there something subtle type theory where allowing this makes a program go to ?
The behavior you're seeing isn't specific to parameter packs. You could make an argument that this should be allowed as well:
protocol P {
init()
}
struct S: P {
init(x: Int = 0) { /* ... */ }
}
The way I understand it, protocol witness matching is based on how a member is declared, not how it can look when it's called.
Could the compiler synthesize a thunk here to use as the protocol witness when there is a compatible but not exact match? Perhaps, but that would need to go through Swift Evolution and would need to make sure it doesn't cause issues for type checker complexity.
I'd say this is a separable issue, given the design choice that the default value is emitted in the caller with its implications for ABI, etc. One can legitimately argue that there isn't a 0-ary initializer in your example, since any trampoline you could write would have that default value in the callee, which is subtly different.
That's not the case for the parent: a variadic initializer really does mean we have a 0-ary initializer with no caveats. I think the answer here is more mundane: it can, it just doesn't--it simply hasn't been implemented (and would require a proposal to do so).
This feels like a distinction without a difference in terms of what a user, not a compiler engineer, would expect. The mismatch is exacerbated by the fact that there's no way for the init() that the conformer has to write to delegate to the init(x: Int) without explicitly restating the default value to disambiguate the overload that they want, which ends up emitting the default value into something akin to the callee anyway.
It's a subtle but user-visible difference on an ABI-stable platform:
public protocol P {
var x: Int { get }
init()
}
// ABI-stable library, version 1
public struct S: P, Equatable {
public var x: Int
public init(x: Int = 42) { self.x = x }
// Hypothetical, or what would be thunked (presumably doesn't compile today):
@_implements(P, init())
public static P_init_witness() -> Self { init() }
}
// ABI-stable library, version 2
public struct S: P, Equatable {
public var x: Int
public init(x: Int = 21) { self.x = x }
// ... all else same
}
// User's app
func f<T: P>() {
let s = S()
let t = T()
print(s.x == t.x) // `true` if recompiled; `false` otherwise
}
(Of course, in present-day Swift, there are non-hypothetical ways of coaxing out this difference; but we're talking here about the specific claim regarding whether we can/can't extend what counts for a 0-ary initializer requirement's implementation, so to go with that I'm sticking with the hypothetical.)
My point here only is that there's really no such wrinkles with parameter packs--we simply don't have a proposal or implementation.