The Future Of [weak self] Rebinding

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

1 Like

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.

5 Likes

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.

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.

2 Likes

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

6 Likes

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

19 Likes

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.

1 Like

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.

3 Likes

I’m also not a fan of new syntax to sugar around a single use-case. I’d go with Option #1.

1 Like

Maybe this is similar to what @anandabits has in mind, but what if instead of a sugared [guard self] construct, there was a modifier keyword used in closure capture lists to indicate that a given value needs to be guard-unwrapped? For example (strawman syntax) –

doSomething() { [required weak self] in
    self.foo()
}

which would be equivalent to

doSomething() { [weak self] in
    guard let `self` = self else { return }
    self.foo()
}

This could be applied to other captured values too, not just self. Any thoughts?

3 Likes

Should discussion of ideas like [guard self] take place in a different thread, perhaps? It’s related but still a bit of an aside from the main issue here, which is whether self should be allowed to be rebound in let/guard statements. That can be considered separately from the bug fix described in the original post and there’s a lot of bike-shedding going on that’s independent of that issue.

3 Likes

@allevato I think discussion of guarded closures should be moved to a separate thread. I’m out of town right now and will dig up the draft proposal I have when I get back home in a couple days. I’ll post that in a new thread for further discussion.

Even in small projects, I run into writing guard let strongSelf = self else { return } often. Supporting guard let self = self else { return } makes the most sense to me. We have not hesitated to fix bugs in the past, and here, the migration strategy is the mere removal of two characters. Deprecate the backtick-hack thingy it in Swift 4.2 or Swift 5, remove later in favour of the real logical syntax.

A possible [guard self] syntax in the capture list should be discussed independently.

1 Like

Currently, you can write let `anyIdentifier` = 1, and it’s accepted. This isn’t a hack, it’s a general feature which allows keywords (EG: in, default) to be used as argument labels and identifiers.

Sorry, I was specifically referring to the usage of self with backticks as the ‘hack’.

Why should we disable this specifically for self, when it works with any other identifier (even when redundant)? Perhaps a warning with a fixit in the short term is sensible, but in the long term, I’m not sure why self needs special-casing like this.

PS: I agree the current behaviour is a bug/hack. I’m just wondering about your suggestion to ‘remove’ this capability once rebinding self is actually allowed.

1 Like

That’s fair. The initial post suggested a migration but like you say, it’s actually consistent with the rest of the language as is. Just supporting guard let self = self else { return } is enough, and frictionless.

4 Likes

Evan Maloney: Upgrade self from weak to strong

1 Like

This proposal is already merged as SE-0079 with “Deferred” status.

4 Likes

Thank you Chris and everyone who gave their input.

In my original post I include “having to do a migration” as a con in option 1. Luckily some of you pointed out that it’s not necessary if self is to be an identifier.

I’ve made a small change to the parser that enables option 1 and keeps the rest of the compiler behavior for now: https://github.com/apple/swift/pull/15306

With this patch, self is not completely a normal identifier yet in the sense that the parser would still consider it a keyword sometimes. But it gets us closer.

7 Likes