If we implicitly widened their arity for no reason you would start to see code size balloon around them.
Today, functions like foo(x: Int = 0, y: Int = 1, z: Int = 2)
do combinatorial thunk explosion. IMO, that is the real issue, and they make default arguments unwieldy regardless of what features they have.
The primary consequence of this proposal is it makes default arguments more friendly, which makes you more likely to use them, and that leads to more explosion, but IMO it's incidental.
The combinatorial explosion is straightforward to solve, although it requires taking a step back to see from a different perspective. If you look broadly at Swift features they are mostly shorthand for some longer, "obvious" implementation. For example, implicit Equatable/Hashable/Codable are shorthands for the obvious implementations. Generics are to some extent shorthand for a templating system, etc.
But "thunks" are not an obvious expansion of default arguments, at least not in the sense of being emitted into the executable with a prologue, etc., as evidenced by all the proposals that 'discover' the explosion problem. I think if you asked someone to tell you what foo(x: Int, y: Int = x
) is "short for", tthey might give you tera's solution. Or they might give you something like this:
protocol FooArgs {
var args: (Int,Int) { get }
}
struct FooOneArg: FooArgs {
let x: Int
var args: (Int, Int) { (x,x) }
}
struct FooTwoArgs {
let x: Int
let y: Int
var args: (Int, Int) { (x,y) }
}
func fooPAT(args: FooArgs) {
let (x,y) = args.args
print(x,y)
}
Most developers who use default arguments really wanted "semantic sugar for one of these".
Technically, in this situation the explosion is now on the types, but since the types are trivial and anonymous we don't need to emit them. We do need to emit default values, e.g. an indirect mapping of fields. But layout could be implicitly a tuple for example. Since the compiler is the only user of the type, protocol witness tables etc. are not needed, nor is anything beyond what the compiler uses internally. So ultimately all the combinatorial bits "boil away".
An expression-based solution like tera's may be simpler than boiling away anonymous types, depending on what features we want to add later.
This does have runtime overhead, which seems in line with Swift features that expand to an implementation. But there are other options, such as
func fooGenerics<A: FooArgs>(args: A) {
let (x,y) = args.args
print(x,y)
}
This has the combinatorial explosion but it's at specialization-time, since specializations are late you only pay for the combinations you use.
But basically, I think thunks are just not a good fit for what people are actually doing with the feature, nor are they in line with how the language is used in general. We should probably fix that, although it is a bit tricky to make the ABI compatible.