Pitch: Syntactic sugar for circumventing closure capture lists

But, say on a closure executed synchronously, if there's a crash here using unowned then there's something far more insidious at play going on. If my program goes haywire – I want it to crash so I can get the logs from the relevant place!

Obj-c could be a nightmare when methods would fail silently messaging nil. On balance, I think I prefer my failures explicit. And – to be fair – Swift offers you the choice. If you want to code with your instances bound weakly – you can.

Also, it really depends on your definition of 'safe'. It's hard to know how a silent failure may manifest itself further down the line. Whilst data loss might not be an explicitly clear consequence of a silently failing closure, perhaps it does result in some inconsistency of state that is nevertheless damaging – and extremely hard to track down.

1 Like

So, I guess the problem here is: now you have an application out there. It doesn't crash at this point. Instead it crashes at some later point, or worse causes some unexpected data loss, because it's in some unexpected state which you've chosen to ignore.

Agreed, screwed either way :frowning:. However, I'd say which option we chose is an important business decision and should not be defaulted due to one option being easier to implement.

That's the point of assertions, errors, logging, etc. The decision isn't "crash or do nothing". You can fail gracefully and leave a trail of information in the wake of the problem, ideally without the end-user knowing anything about it.

A part of my UI that's already dismissed not updating properly because of some strange edge case I didn't think of is always preferable to it just crashing.

2 Likes

100%. There are use-cases for strong, unowned and weak. Hence need them all.

Also, if you want it to crash, you can still use [weak self] and just crash manually.

But why create assertions and loggings for something that – assuming the integrity of the system – should never happen.

If the integrity of the system is compromised. I want it to crash. There's no point putting a plaster on a fatal wound.

That's a great use case for weak.

I agree there's a use-case for [weak self] in guard let self = self else { fatalError() }.

I don't see why it should get a sugary synonym.

If you have a perfect understanding of the OS, device, runtime, language, and APIs you're working with, past present and future, and you never change your code, then you have nothing to worry about. For me assumptions about those things have bit me in the butt enough times that I avoid them anytime I can.

However use cases and expertise vary, so if that calculated risk makes sense, go for it.

Pedantic nuance aside :nerd_face:, My recommendation to new users of the language (and I'm guessing yours?) would be to just use [weak self] due to how detailed one's understanding needs to be in order to use [unowned self] safely.

1 Like

I guess what I would say is that it works – until it doesn't!

I think the risk is, what this offers is the illusion of safety, not actual safety. And that false sense of security will burn them in the end. All is well until they have to track down some esoteric bug, or worse data loss, for which there is no crash log or call stack.

If they're trained to use weak without really understanding why, this could be particularly nasty and difficult to diagnose.

Erlang is really interesting in that it's been designed with the philosophy let it crash, which is all the more interesting when they have this reputation for 'nine nines' reliability. Telecoms software with guaranteed uptime of 99.9999999%. They manage this using an actor model and a network of 'supervisors' that reboot processes when they crash. The nature of the supervisors mean that any crashes are reported but the system is self-healing.

I know Chris Lattner was talking about the potential of bringing the actor model to Swift but I don't think I heard mention of supervisors. I don't know how relevant it would be for desktop/mobile class consumer software but interesting nevertheless.

In the medium-term I'm interested in how the await/async coroutine stuff pans out, as perhaps that will offer an alternative to the closure based syntax for instance methods owned directly/indirectly by self. I've not used it in any other language myself – but there's the potential to design it in a way that is mindful of the use-cases we've mentioned here.

Isn’t unowned just force-unwrapped weak? If so, why don’t we reuse the syntax we all know and love? In other words, unowned could be written as “foo!” (where “foo” is weak) instead of just “foo”. Also weak properties, I wonder if there is a difference is between “weak Foo!” and “unowned Foo”. Is there?

1 Like

I don't believe so, no.

As I understand, for practical purposes a weak property is actually a wrapped unowned property (I imagine some kind of raw pointer under the hood) which requires some additional plumbing provided during compilation to safely determine whether or not the subject has been deallocated.

Therefore there is actually a non-negligible performance cost to using weak over unowned – I remember hearing that this has been significantly reduced in recent times, however.

3 Likes

So from a practical perspective, could unowned find a newly created instance at the same address, and substitute that instead of the original? While weak would detect that the original was deallocated and reliably produce nil?

I write closures that are both escaping and capture self all the time. In fact, I usually want that for async operations that are guaranteed to call a completion handler exactly once. Then I want the closure to capture self.

Once the completion handler is called, it may hold the only still-alive reference to self, but that's fine, since the escaping closure will go out of memory immediately after being called, releasing the sole existing reference to self. Using [weak self] in these closures, may be fine, but it also means that the completion block may not be called at all(!)

2 Likes

That's what I find myself doing as well lately. It does mean that you cannot just default to weak everywhere but have to think case-by-case (and on edits too!), but that's what actually should be done, so... :slight_smile:

I don't know too much about Erlang but this approach makes sense with an actor model, but without it I'm not sure... in usual applications programming a single crash anywhere brings the whole castle down :\ At most we can simulate recoverable crashes through error propagation

I mention it only as a tangental point of interest: there is talk of Swift adopting the actor model some time in the future and I'm curious if that would enable Erlang/OTP like supervision trees.

And that's the point really: blindly adhering to weakly binding self, at best, encourages the non-handling of error states, and at worst creates difficult to diagnose bugs and the risk of unexpected states and potential data loss.

That's not to say there aren't many circumstances where this is acceptable – simply that that as developers we should be mindful of exactly what those circumstances are.

1 Like

Intuitively, I imagine that would be possible, but that the chances in most instances would be infinitesimally small. Would be curious to know how a memory address is validated. Maybe someone who has a better idea of how things work under the hood would know.

Not sure if it's what you meant, but I'm talking about retaining self, not simply capturing self. Pedantic, but important difference :slight_smile:

I believe the closure will always run, that's essentially a variable passed to the function (correct me if I'm wrong). The only thing that can't happen is using methods/properties of the now-deallocated object. And more generally my point wasn't that nobody does it so it should be removed or something, just that it's the exception not the rule.

That's kind of my point. You have to think through how something works in order to use the default safely. Something a new user would be unlikely to understand. The option that requires thinking through should be opt-in, not the default.

1 Like

This is exactly my feeling. I see a lot of discussion about ‘use unowned because it should never happen’. But 90% of closures are not like that, they’re closures than can be called after self is dealllocated (aka a VC is gone) and thus in that 90% the gusrd self dance is actually the best approach.

A [guard self] capture list would reduce the boilerplate and make it safe. That’s already been proposed in the past and it didn’t go anyway. Maybe now is a better time.

Also, I’m still now sure what syntax could be don to improve captured self when factoring out closures on methods.

3 Likes