I'm afraid I'm going to be a bit of a wet blanket here, but I don't think this would be a good addition to the language—at least not right now—for a number of reasons.
Code is read far more often than it is written. This change certainly makes code easier and faster to write, but that isn't an end unto itself for Swift; the goal is that the resulting code is also easier to understand.
This change doesn't pass that test. When looking at a closure, the only thing determining whether the statements inside it execute at all under certain circumstances is potentially a single keyword in the capture list. And if the suggested alternative of if self
or if weak self
is chosen instead of guard self
(I agree with @xwu's reasoning why guard
is a poor choice here), then we're talking about two non-whitespace characters changing whether an entire closure body is executed. I worry that the difference is very easy to overlook and it's burying information that should be more explicit.
It also seems to be somewhat common that people overuse [weak self]
when they don't actually need it. Rather than encourage users to consider the implications of the memory ownership they choose, I worry that this feature just lets them dig deeper into bad programming practices—they'll just reach for [guard self]
by default, it becomes the new [weak self]
, and then no closures would have any explicit control flow acknowledging the possibility that self
or whatever was captured could have been released.
How would this feature interact with debuggers? If you have an explicit guard let self = self else { ... }
statement and you suspect your closure is getting called after self
is released when you expect it to still be alive, you can set a breakpoint on it and inspect the state of your program. How would you provide a comparable debugging experience if the control flow is synthesized by the compiler?
Philosophically, I don't think it makes sense to introduce special syntax into capture lists that conflates control flow and memory ownership. To my knowledge, no other construct in Swift does this. The closest would be the standard if/guard let x = x
conditional bindings, but those work for any optional value—not just weak captures—so it's about general value presence and not memory ownership, and they still make the control flow (if/guard
) independently clear from the binding (let x = x
).
Moreover, we have to ask whether it's healthy for Swift long-term to extend it with special cases for very narrow use cases. guard let self = self else { return }
uses standard language features that apply in numerous other different situations. But this feature:
- ...only applies to closures
- ...which capture references weakly
- ...and which return
Void
- ...and where the behavior you want is to skip execution entirely.
We can speak of the complexity of the language and the compiler, or we can speak of the complexity of the documentation and the cognitive load for learners of Swift to understand which situations this feature applies and doesn't apply. I think this feature would be an increase in both.
There's a thread from a few months ago about another related topic that comes up frequently: simplifying if let x = x
syntax. There's a lot of discussion there about the challenges involved, and in that thread at least a couple members of the core team have said they feel that the idea of simplifying optional bindings is worth revisiting. IMO, it feels premature to try to remove the boilerplate for this specific case before looking at the boilerplate for optional bindings in general. It's possible that a solution to the overall problem could reduce the boilerplate for this case such that it is less objectionable, and without completely burying important control flow information. Focusing on weak captures first and then doing general optional bindings later could end up with two very different constructs for similar features, making the language incongruous and difficult to learn/understand.