I have a question regarding the correct usage of protocol extensions for adding a default implementation of a function in a protocol with default arguments, given that default arguments is not something protocols support upon the declaration of the protocol itself. Here is the code example in a file we call `main.swift`.
protocol A {
func doSomething(with aString: String)
}
extension A {
func doSomething(with aString: String = "Default") {
doSomething(with: aString)
}
}
struct AImpl: A { } // Why does this compile given that doSomething(with aString: String) is not implemented?
AImpl().doSomething() // Leads to segmentation fault upon runtime
This program can be compiled and run as such, upon which the program crashes at runtime with a segmentation fault:
Interestingly, when compiled with `-O`, the program does not crash with a segmentation fault, but rather (seemingly) just executes without ever terminating. I guess this is because of infinite recursion.
To summarise:
Why does the program compile when `AImpl` does not implement doSomething(with aString: String)?
What is the correct way to approach adding default parameters for protocol functions in an extension without risking making conformees to said protocol prone to programming errors that result in infinite recursions?
Edit:
I should add that implementing the protocol function results in the program not crashing:
While this is not really surprising, if calling the extension function lead to recursion, does that mean the compiler treated the two functions as one, that is, having the same signature? If that is the case, I’m surprised it allows for the existence of two functions with the same signature. I may be missing the obvious here, but I find it confusing.
Yes, as you surmised, your default implementation is also used to satisfy the protocol requirement so the compiler doesn't require you to implement it in conforming types and you end up with infinite recursion.
There is no good solution for this case currently as far as I am aware.
You either need to manually make sure that each conforming type implements the protocol requirement or use different names for the protocol requirement and the default implementation with the default argument (or avoid the default argument entirely or move it to a wrapper).
There have been several previous discussions about allowing default arguments in protocol requirements directly (example) which would sidestep this issue.
Alternatively if you had to manually mark implementations of protocol requirements, the default implementation here could avoid that so conforming types would still be required to add the requirement themselves. But that would require a massive source breaking change, and seems rather unlikely to happen. [See here] (https://forums.swift.org/t/keyword-for-protocol-methods) for an example of a similar discussion.
Perhaps the most realistic workaround would be an attribute that could be used on the default implementation to mark it as not satisfying the protocol requirement. For example, a @_notImplementing companion to @_implements.
This is not a protocol extension-specific problem.
Default parameters are a way to call one function with different parameters—not a set of overloads. You can create explicit overloads that do the same thing.
In your case:
extension A {
func doSomething() {
doSomething(with: "Default")
}
}
Abstracted to the general case:
func f(a: some Any = (), b: some Any = ()) {
print(a, b)
}
func f(a: some Any) {
f(a: a, b: ())
}
Having to keep the defaults in sync is a problem that should be addressed by tooling, but it works.
As others have pointed out, this is a valid default implementation of the protocol requirement, but one which you can determine by inspection to be infinitely recursive. There's nothing specific to protocol requirements or their default requirements: Swift doesn't prevent you from writing functions that call themselves (nor should it), although in the simplest cases of unconditional infinite recursion a best-effort warning might be helpful—
func f(_ x: Int) { f(x) } // Valid. Probably not what you want.
If what you're after is a way to allow someone to write doSomething() on any conforming type and have that forward to doSomething(with:), then you'll need to have another declaration. Either declare it on your protocol explicitly (as shown below), thereby allowing conforming types to override the default value, or alternatively only provide a non-customizable protocol extension method without declaring it as a protocol requirement (as shown by @Danny).
protocol A {
func doSomething()
func doSomething(with aString: String)
}
extension A {
func doSomething() { doSomething(with: "Default") }
}
protocol A {
func doSomething(with aString: String)
}
extension A {
func doSomething() {
doSomething(with: "Default")
}
}
If you want to create a default doSomething() on all conformers of the protocol, this creates a new non-overloading function as an extension, which delegates to the protocol requirement. All conformers must provide an implementation for doSomething(with:) but they all get the other one for free.
If you want to allow conformers to override the default one, add it as a protocol requirement, but keep the default implementation in the extension, as per @xwu 's suggestion above.
This is because the optimizer is able to see through the recursive calls and transform them into a loop. Looking at the assembly for this snippet ( Compiler Explorer ):
output.b() -> ():
.LBB6_1:
jmp .LBB6_1
You’ll see that it was indeed optimized down to an infinite loop that keeps jumping back to the first instruction.
I’ve always really wished for a way to provide defaults directly on the protocol requirement. The extra boilerplate to define a passthrough in an extension is a pain to keep up-to-date, and sometimes isn’t possible in cases like this where it would sometimes become recursive.