That too would have implications where a subtle seemingly innocuous change in a function could change the analysis and make the compiler no longer call the setter. Since the setter can have important side effects (even when called with the same value), it might not be advisable to skip it on the whim of the compiler.
I'll bring up the idea of modifiers. Here's some of the possibilities:
-
@always inout: value always mutated or setter always called, regardless of throws or if there was an actual change (what we have now). -
@success inout: value mutated or setter called only on success, never on failure. -
@frail inout: value mutated or setter called on success if the compiler thinks the value might have changed, but this might or might not happen on failure (whatever is convenient for optimization).
Making @frail the default makes me uneasy. It's the most optimizable variant, but also the less predictable one. It's a hole in the contract of a function (something is left undefined).
But here's an idea: maybe make @frail the default, but make the compiler track in the caller accesses to values written from a @frail inout and emit a warning/error if used in the non-success path (when the value is not well-defined). You could add @always or @success in the signature of the callee to make the value well-defined for the error path and avoid the warning/error.
Edit: I realize now this is a nice solution to keep the value predictable on abnormal terminations (throws), but it does not make the side effects of the setter more deterministic in the @frail case. If we had @pure properties we could restrict @frail inout to these, but that restriction wouldn't be good for source-compatibility.