[swift-evolution] [Draft] Guarded closures and `@guarded` arguments
Brent Royal-Gordon
brent at architechies.com
Sun Feb 19 23:35:11 CST 2017
> On Feb 19, 2017, at 2:57 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
>
> A guarded closure may be created by prefixing a bound instance method reference with the `?` sigil:
>
> ```swift
> let guarded = ?myObject.myMethod
>
> // desugars to:
> let guarded = { [weak myObject] in
> guard let myObejct = myObject else {
> return
> }
> myObject.myMethod()
> }
> ```
I like this in principle, but I don't like the syntax. The `?` is cryptic and looks pretty strange in prefix position. I think what I'd like to see is:
let guarded = weak myObject.myMethod
Or:
let guarded = weak(myObject).myMethod
This second alternative could give us the opportunity to specify a default return value if we'd like:
let guarded = weak(myObject, else: nil).myMethod
> A guarded closure may be created by prefixing an inline closure with the `?` sigil:
>
> ```swift
> let guarded = ?{ flag in
> flag ? someMethod() : someOtherMethod()
> }
>
> // desugars to:
> let guarded = { [weak self] flag in
> guard let strongSelf = self else {
> return
> }
>
> flag ? strongSelf.someMethod() : strongSelf.someOtherMethod()
> ```
This runs into the same problem as previous suggestions for addressing the weak-strong dance: There's no particular reason to privilege this behavior over any other.
I think I'd prefer to use a slightly wordier two-part solution here:
1. If the capture list is just `[weak]` (or `[unowned]`), that's the default for all variables.
2. A `guard` with no condition—in other words, `guard else`—tries to strengthen all weakly captured variables and enters the `else` block if any fail. (Alternative: `guard let else`.)
That makes your example into:
let guarded = { [weak] flag in
guard else { return }
flag ? self.someMethod() : self.someOtherMethod()
}
This is many more characters, but I think it's also much clearer about what's going on.
> #### Self reference in escaping guarded closures
>
> Guarded closures do not extend the lifetime of any objects unless a `strong` capture is specified in the capture list. Because this is the case users are not required to explicitly reference self using the `self.` prefix inside a guarded closure.
I think this is a bad idea, because the implicit `self` problem still exists here—it just has a slightly different shape. Here, if you accidentally use `self` without realizing it, your closure mysteriously doesn't get executed. This misbehavior is at least easier to notice, but it's still going to be hard to track down, since it's caused by something invisible in the code.
> ### `@guarded`
>
> This proposal introduces the ability for arguments of function type to be annotated with the `@guarded` attribute. When `@guarded` is used the caller must supply a guarded or non-capturing closure as an argument. This ensures that users of the API think very carefully before providing an argument that captures a strong reference, and requires them to explictly state their intent when they wish to do so.
Even in this modified form, I think this is a bad idea. The person writing the closure is the one who knows whether they want it to extend objects' lifetimes. The library author accepting the closure has no idea whether the objects the closure captures are models it needs or controllers it shouldn't keep—or, for that matter, models it shouldn't keep or controllers it needs.
If we really think that this is a serious problem, we should change `@escaping` closures to always capture weak unless they're explicitly told to capture strong and be done with it. If that sounds like a hare-brained idea, then we shouldn't do it half the time and not do it the other half.
--
Brent Royal-Gordon
Architechies
More information about the swift-evolution
mailing list