Hello,
The code base I'm currently working on has a few "editor" protocols that look like the following:
protocol IntEditor {
var initialValue: Int? { get }
func setValue(_ value: Int)
}
As a convenience, they all come with a companion type-erased struct:
struct AnyIntEditor: IntEditor {
var initialValue: Int?
var setValue: (_ value: Int) -> Void
func setValue(_ value: Int) {
setValue(value)
}
}
Those work quite well:
let editor: IntEditor = AnyIntEditor(
initialValue: nil,
setValue: { print($0) })
editor.setValue(12) // prints "12"
Now, the definition of AnyIntEditor.setValue(_:) is very troubling, because it looks like it starts an infinite loop. But it does not, as it actually calls the property (Xcode 11.3):
struct AnyIntEditor: IntEditor {
var initialValue: Int?
var setValue: (_ value: Int) -> Void
func setValue(_ value: Int) {
setValue(value) // calls the property
}
}
So here is my question: is it a guaranteed behavior, or some unintended side-effect of an implementation detail in the compiler that no one should rely on?
I found this in the compiler source code, in ConstraintSystem::compareSolutions:
[...] a property is better than a method (because a method is usually immediately invoked).
Of course this piece of code may be unrelated to my question. But if it is, it looks that the answer is a positive yes. I admit that I'd prefer finding something which is more convincing, such as a test, or a confirmation from a knowledgable person. May I summon @Slava_Pestov?
This should be what I was looking for:
let ambValue = ambiguousWithVar // no-warning - var preferred
let ambValueChecked: Int = ambValue
ambiguousWithVar = 42 // no-warning
ambiguousWithVar(true) // no-warning
So... I consider my question answered 
It is worth noting that this behavior does not extend to function signatures that have an empty argument list:
protocol IntEditor {
func initialValue() -> Int?
func setValue(_ value: Int)
}
struct AnyIntEditor: IntEditor {
var initialValue: () -> Int?
var setValue: (_ value: Int) -> Void
// Compiler error: Invalid redeclaration of 'initialValue()
func initialValue() -> Int? { initialValue() }
// OK
func setValue(_ value: Int) { setValue(value) }
}
This is an inconsistency in the behavior of the compiler, which may well deserve an issue. I won't open any, because this inconsistency may well be required as a support for other more important compiler behaviors. I just don't know.
Yet, I find this topic interesting, because the current state of the compiler gives an objective argument against sharing the same name for a closure property and a method, which is that this sharing is not guaranteed to be accepted by the compiler for all signatures. The pattern does not always work well.
There is a verbose workaround which preserves the pattern, though:
protocol IntEditor {
func initialValue() -> Int?
func setValue(_ value: Int)
}
struct AnyIntEditor: IntEditor {
private var _initialValue: () -> Int?
private var _setValue: (_ value: Int) -> Void
init(
initialValue: @escaping () -> Int?,
setValue: @escaping (_ value: Int) -> Void)
{
_initialValue = initialValue
_setValue = setValue
}
func initialValue() -> Int? { _initialValue() }
func setValue(_ value: Int) { _setValue(value) }
}
let editor: IntEditor = AnyIntEditor(
initialValue: { nil },
setValue: { print($0) })
editor.setValue(12) // prints "12"
glessard
(Guillaume Lessard)
6
Maybe a clarification might come into existence if an issue were created.