The Future Of [weak self] Rebinding


(Daniel Duan) #1

Summary

For a few major Swift versions the following code is allowed to work with weakly captuered self:

guard let `self` = self else { /* … */ }
// proceed with non-optional self

It’s a wide-spread pattern today. The consenses from past discussions is that this has been a compiler bug. Ways to fix it have been suggested.

Past Discussions

There was a SE discussion about this and an older discussion dated all the way back to 2015. There’s at least one Twitter conversation I can find.

Motivation

At various times it was pointed out this ought not to be an Swift Evolution topic. But the source-compatibility story for Swift has changed over time. As weakly captured self is pervasive in Swift code, this is an worthy topic to formally settle (unclear in what way) as part of the language.

Personally, I work on codebases in which the pattern mentioned in Summary is widely adopted. Recently, my team started to re-evaluate the decision of endorsing this pattern for a very specific reason: an lldb bug in which the rebound self makes lldb extremly unhappy. It’s reasonable to say that whether/how this lldb bug will be fix is tied to the future of this langauge construct. Therefore I’d like a reading on the barometer in the community.

Options For Change

1. Allow self Shadowing

Enhance the compiler such that guard let self = self else { /* … */ } works. If I’m not mistaken, this is the option many from the compiler team suggested in the past.

Pros: it appears to be “the right thing” from a design point-of-view; there’s a clear migration path for the existing “bug”.

Con: we’ll have to do an migration.

2. Make the bug a feature

The pattern mentioned in the Summary section is widely used. In one of my team’s projects, there are 50 occurances in a 48k LOC Swift-only module, not counting blanks and comments. A quick search for the exact phrase on Twitter reveals that it’s been shared as a “pro tip” over the years it has existed. So it’s not unreasonable to have a SE proposal to make this syntax an official part of the language.

Pro: good source compatibility.

Cons: murkier language design. self becomes even more special. The role of backticks becomes inconsistent.

3. No support for weak self rebinding

We can simply migrate users away from using this pattern and decide whether/when/how to improve this pattern later. Swift users can use other identifiers for weak self rebinding.

Pros: gives a clear signal to early adopters in the community to move away from exploiting the compiler bug; clear migration path; probably simple to implement.

Cons: we’ll have to do an migration; some may think this creates an inconvenience.

Discuss!

Please read up on past discussions (they are short, relatively speaking) and share your thoughts.


Guarded closures
Circling back to `with`
Guarded closures
(Tony Allevato) #2

Ah, I’ve had this discussion a few times in code reviews of our code bases, because it’s a pattern that some teams have picked up. I’m not a fan of it in its current form, for the reasons you mentioned.

Personally, I think #1 is the best option. The pattern of capturing weak self and then exiting early if it’s nil is common and important enough to using the language correctly that we should optimize for it. The current “pro-tip” is a syntactic hack, and the alternative of forcing the user to choose a contrived name like self_ or strongSelf doesn’t align with the importance of this design pattern.

One sticking point that I can imagine is that rebinding of self looks like reassignment of self in value-type contexts, but since this situation only extends to reference types, technically you are reassigning the self reference to an unwrapped version of itself to keep it alive within the scope, so the analogy still holds.

You didn’t mention if let self = self { ... }; would this proposal apply equally to rebinding self inside an if block, or do you only plan to support the guard form?


(Daniel Duan) #3

You didn’t mention if let self = self { … }; would this proposal apply equally to rebinding self inside an if block, or do you only plan to support the guard form?

This was not intended to be a proposal. I personally think if let ought to work if option 1 is chosen.


(Erik Little) #4

I’m in favor of option 1. Although if and when Swift gets a powerful macro system where you could inject code into blocks I can forsee being able to something crazy like:

someFunctionCall {#guardingWeak(self) x in

}

(James Froggatt) #5

Rather than macros, we should really just make [guard self] a language feature.


(Xiaodi Wu) #6

Not sure how I feel about inventing new syntax for one specific use case.

guard let self = self seems like what users will naturally try, extrapolating from what they know about the language. I agree that option 1 should just be made to work. (And equally, if let self = self.)


(James Froggatt) #7

Fair enough, but it’s a very common use case when passing closures to objects owned by self, such as pretty much everything in a UIViewController. Would you propose use of [unowned self] for this?


(Erik Little) #8

It’s been my experience that unowned self, no matter how confident you think will not crash, will still come back to haunt you. I almost never use unowned in my codebases.


(James Froggatt) #9

Which is pretty much my point. There is a temptation to use unowned, rather than weak + a line for guard, just because getting the proper behaviour (do nothing if self is nil) is so verbose. Not to mention that the current shadowing of self is a compiler bug that many will not have discovered. I’d say the current situation is actively harmful in these cases.


(Jarod Long) #10

Very much agreed – this is such a common pattern in app development that the convenience would be well worth the additional feature, especially if [guard self] eliminated the need for the self prefix inside the closure. The concept of guard translates very well into this context imo.

I’d be really happy to go directly to that solution as a fix for this bug. But I also feel like guard let self = self is something that makes sense and should work.


(Matthew Johnson) #11

I also agree that option 1 seems like the best direction. This direction would also support API designs which accept and capture (weakly) a context along with a callback and only invoke the callback when the context is still around (passing a strong reference to the callback). With APIs like this you might have code that looks like this:

myAPI.invokeAsyncMethod(context: self) { self, result in
   self.handleResult(result)
}

(Erik Little) #12

Like what @xwu says. I don’t think this should be its own construct in and of itself. I think it should be left to a macro-system if that ever comes along and provides enough power to do this, or just let people do guard let self = self else { return } like option 1 says. I don’t think using an explicit guard is that bad to begin with.

Really not sure how I feel about that. While when I first started writing Swift I thought it was annoying to have to reference self in closures, but now that I know why it does that, I agree that it is the way to go.


(James Froggatt) #13

Short of introducing [guard self], which would require an evolution proposal, I’d also go for #1.


(Matthew Johnson) #14

I was working on a proposal for guarded closures during the Swift 4 cycle but tabled it. I have come to believe that there are often better ways to handle callbacks in most cases. However, this style of code is still pervasive in Swift and will be for some time to come. I do still believe that code written in this style could benefit from improved language support.

If this direction gains support and is on the table for consideration in Swift 5 I would be happy to brush off the proposal and finish the updated draft I had been working on.


(Jarod Long) #15

I definitely agree with the feature in general, but if the user has added [guard self], I think it’s safe to assume that they’ve considered the capturing semantics of the closure. The risk of an accidental retain cycle is gone in this scenario anyways, so the intent of the feature doesn’t really apply anymore.


(Dan Stenmark) #16

Of the choices, I’d +1 the first option. That said, given the specialness of self, what would be the effect of:

let foo: Int? = bar()
guard let self = foo else { /*...*/ }

Would that be allowable syntax? If so, I’d still have qualms about this.

While the [guard self] pitch sounds interesting, I wonder if we can also get away with:

guard self else { /*...*/ }

I know this kind of shorthand binding has been rejected in the past when pitched in the context of local variables and properties, but self feels like the one place it would work well.


(Davide De Franceschi) #17

What would [guard self] do when the closure returns some value and not Void?


(Chris Lattner) #18

Thank you for bringing this up, and you are right that time does change things. To be completely clear, I am speaking only for myself in this post, not the core team or anyone else.

A couple of years ago, I was completely against anything like this, feeling strongly that ‘self’ as a keyword was magical and privileged. Various things have changed that opinion, including the introduction of guard.

I now agree with you that this is important, and that we should do the obvious thing here: just make self be a normal identifier which is implicitly injected into methods. This is your #1 option, which would allow rebinding with if/let, guard, shadowing and everything else.

I am pretty strongly opposed to #2 (make the bug a feature) as the bug is an ugly gross workaround. We should just move forward and accept the obvious code that users will continue to write. I don’t see any obvious downside of doing so.

-Chris


Circling back to `with`
(Jarod Long) #19

Good question – I think it would have to be forbidden in that case. In my personal experience, the types of closures that I use [weak self] in are almost (maybe entirely) exclusively callback-style closures that don’t return anything, so I see this as an edge case.


(Hooman Mehr) #20

I fully support this as the correct approach in general, not just a special case for closures. Still, we may support a [guard self] for closures as a sugar for the closures that have a return type of either optional or Void.