inout
parameters are a great way to atomically update a value with multiple mutations. However, it has quite a downside: if you call an inout
function, the value is updated, trigger observations, even if the function never actually mutates the values. This can lead to quite a lot of duplicate observations and seems very inefficient. Aside from preserving existing semantics, is there a reason it has to work this way, or could it be optimized out?
Without inlining, the outer function can’t tell if the value was ever changed by the inner function, so it conservatively assumes that it does. An alternate world could have a hidden out-parameter for every inout parameter that would only get set if the parameter is actually modified, but that’d be a waste in all the cases where the outer function does not care, or where the modification is unconditional.
It is definitely too late for Swift to change the behavior of inout
; the fact that it always calls the setter is load-bearing in multiple projects by now.
EDIT: if this is a deal-breaker for you, provide a callback instead of using inout
! Then you know for sure whether the modification happened. Or do the inout
yourself (pass in, return out).
With inlining, is that elision actually possible now, or was that hypothetical?
In any case, any way to work around this behavior? It makes observation of these values rather inefficient.
No, sorry, that was hypothetical. As stated, this is semantic behavior of the language that must not change under optimization. You cannot “work around” it without manual intervention, because no part of the system has enough information to decide that (a) nothing happened, and (b) you wanted to treat “nothing happened” specially.
Can you envision a workaround that doesn't require Equatable
conformance and some sort of manual deduplication?
"Provide a callback instead of using inout
" is the cleanest I've got. The language just doesn't have a cross-function-boundary construct that both gives you Law of Exclusivity guarantees (to avoid spurious copies-on-write) and also distinguishes whether a write happened or not.
There are other possible constructions, but nothing as plain as inout
. On the evolution side, some sort of writeTrackingInout
could theoretically be added to the language and to the design of _modify
accessors, at the cost of that extra out-parameter alluded to, but that is a non-trivial amount of work to design and implement. Something the next language might want to consider up front.
Ultimately, though, the inout
we have is the wrong tool for what you want. If you're passing a property with side-effecting accessors, you're not getting the benefit of in-place modification anyway. If Swift didn't let you use inout
with such properties, you'd manually copy into a local, then modify that, and have the function return whether or not you need to write back. Or pass a value in, and then return an optional value out.