An upcoming proposal for simplifying leak free, safe closures.


(Christopher Kornher) #1

I may be too late for Swift 3, but I am planning to propose changes to the default behavior for closures capturing object references. The introduction of Swift Playgrounds has raised the importance of simplifying the coding of leak-free, crash-free closures. New developers should not have to understand closure memory management to start writing useful and correct code.

The topic of the closure weak/strong dance has been discussed on this list before. This proposal differs from previous proposals in that it will eliminate the dance altogether by default. I am very interested in hearing others’ opinions as to whether the benefits outweigh the costs of various options.

I have found that Swift’s capture lists and rules are a bit of a mystery to many experienced developers, even though Swift’s closure capture rules are very similar to those of Objective-C. Capture lists are probably thought of as opaque incantations by many new Swift developers if they are aware of them at all. Capture lists should, ideally, not be needed for sensible and safe default behavior.

This discussion is iOS / OS X centric and uses terms from those domains, but these issues should be applicable to almost any codebase that uses closures capturing object references.

Capture lists are required by the fact that object references are captured as `strong` by default, often leading to strong reference cycles and memory leaks.

Use of ‘unowned’

···

————————
Many examples of using closures capture self as `unowned`. Often, this pattern does not scale well beyond simple examples. iOS and MacOS applications with dynamic UIs, for example, switch between numerous views and view controllers. These objects are dereferenced and reclaimed when they are no longer needed. Closures capturing these objects as `unowned` crash when the references are accessed.

Unfortunately, ‘unowned’ captures are tempting because they eliminate `guard` and `if let` constructs and avoid littering code with optional unwrapping. They are also slightly more performant, but this difference is probably negligible in most application code.

Capturing escaping object references as `unowned` is only safe when object lifetimes are perfectly understood. In complex systems, it is difficult to predict execution order. Even if object lifetimes are perfectly understood when code is originally written, seemingly innocuous changes to complex systems can negate original assumptions.

For these reasons, I believe that capturing object references as `unowned` should be considered an advanced optimization technique.

I now routinely create closures that capture `self` and other object references as ‘weak’ even if I think that I feel that `unowned ` would be safe. This may not be the absolutely most performant solution, but it is straightforward and robust.

The core proposal:
——————

Closures capturing object references should automatically capture all object references as weak.

The closure defined in:

class ClosureOwner2 {
    var aClosure: (() -> Void)?
    
    func aMethod() {
        aClosure = { [weak self] in
           self?.someOtherMethod()
       }
    }

    func someOtherMethod(){}
}

would normally be written as:

        aClosure = {
           self?.someOtherMethod()
        }

Closures that can be optimized to safely capture object references as `unowned` can use the current syntax.

Swift 2 closure without explicit capture lists for object references will not compile.

Capturing strong object references can be very useful in certain circumstances and have a straightforward syntax:

        aClosure = { [strong self] in
           self.someOtherMethod()
        }

Alternatives / Modifications / Improvements(?):
—————————————————————

1) Closures with object references could be simplified further by implicitly including ‘let’ guards for all object references:

        aClosure = {
           // This is included implicitly at the top of the closure:
  // guard let strongSelf = self else { return }

            /*strongSelf*/ self.someOtherMethod()
  print( “This will not appear when self is nil.” )

  … other uses of strongSelf…

        }

This would have the effect of making the execution of the closure dependent upon the successful unwrapping of all of its object references. Object references that are not required to unwrap successfully can be captured as `weak’ if desired.

2) Some of the magic in #1 could be eliminated by introducing a new capture type: ‘required’ to specify ‘weak guarded’ captures, allowing the example closure to be written:

        aClosure = { [required self] in
           self.someOtherMethod()
	print( “This will not appear when self is nil.” )
	print( “This is not the default behavior and the reason for this is clearly stated.” ) 
        }

This reduces the amount of code required, but may increase the cognitive burden over using the current syntax.

`required` is called `guard` in the “Simplified notation” proposal: https://gist.github.com/emaloney/d34ac9b134ece7c60440

This proposal will differ from the “Simplified notation” proposal in that all object references would be captured as `weak` by default in all closures.

Summary
—————-
Closures with objects references occur frequently in Swift code and the use of closures is probably only going to increase. Current capture list rules are not developer friendly and force developers to deal with subtle asynchronous memory management issues. This increases the cognitive burden particularly for new developers.

Closures should be safe and straightforward by default.

Enhancement #1 without #2 would probably be the easiest option for new developers and result the smallest code size. The question is: is it too “magical” and are the changes in execution behavior going to be significant in practice?

The rules for closure object references with enhancement #1 can be stated succinctly:

By default:

Closures do not affect reference counts of the objects that they reference.
All object references used within a closure must unwrap successfully for the closure to execute.

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?


(Russ Bishop) #2

I may be too late for Swift 3, but I am planning to propose changes to the default behavior for closures capturing object references. The introduction of Swift Playgrounds has raised the importance of simplifying the coding of leak-free, crash-free closures. New developers should not have to understand closure memory management to start writing useful and correct code.

The topic of the closure weak/strong dance has been discussed on this list before. This proposal differs from previous proposals in that it will eliminate the dance altogether by default. I am very interested in hearing others’ opinions as to whether the benefits outweigh the costs of various options.

The problem is that strong reference capture is probably the far more common case.

If you wanted to say that @noescape closures capture references strongly by default, while escaping closures capture them weakly by default that may be closer to reasonable for most situations but now you have magic behavior with an even greater cognitive overhead. (I wonder if it would be more common that @noescape captures strongly, escaping captures self weak by default but other objects strongly… of course that would have even worse cognitive overhead.)

No matter what, without a garbage collector you are stuck with sub-optimal solutions for fixing reference cycles. I could imagine a language feature that automatically detected trivial cycles (A -> B -> A) but anything more complex just becomes a form of garbage collection anyway.

I don’t think there is a way to square this circle. Either you have one “automagic” behavior that is wrong for some cases (whether strong or weak is the default), or you require everyone to spam their closures with explicit capture annotations even if there’s a default “capture everything strongly” variant (see C++ lambdas).

Use of ‘unowned’
————————

I now routinely create closures that capture `self` and other object references as ‘weak’ even if I think that I feel that `unowned ` would be safe. This may not be the absolutely most performant solution, but it is straightforward and robust.

I agree and our team has adopted the rule that use of unowned is not allowed unless the declaration is private and there is profiler proof that it represents a performance problem, and if used warning comments must be placed in the code. Weak is almost never a performance problem and eliminates the risk of a crash, so it is highly preferable to unowned.

I’d go so far as to say unowned should be removed; let the user use Unmanaged<T> if they need to capture an unowned reference. They’re entering expert territory anyway.

The core proposal:
——————

Closures capturing object references should automatically capture all object references as weak.

This becomes a form of the Objective-C messages-to-nil-do-nothing problem where you don’t crash but your closure doesn’t do any work (or does half the work!) because the reference(s) are/become nil. It doesn’t save you from reasoning about object lifetime because it is just as easy for the closure to capture the last reference to an object or for the lifetime to differ from the closure lifetime. You’re just trading reference cycles for a different problem.

2) Some of the magic in #1 could be eliminated by introducing a new capture type: ‘required’ to specify ‘weak guarded’ captures, allowing the example closure to be written:

This has been debated before. I support the idea of a “required” capture specifier but IIRC the core team was not supportive of the idea because it adds another layer of magic on the existing magic (object deallocated magically means your closure never executes).

Russ

···

On Jun 26, 2016, at 12:10 PM, Christopher Kornher via swift-evolution <swift-evolution@swift.org> wrote:


(Charles Srstka) #3

I like it. Accidental capture of a variable is probably one of the primary causes of retain cycles in modern Swift code. Requiring the capture to be explicit would take a lot of the surprise out of it and force the developer to think about the capture semantics.

+1.

Charles

···

On Jun 26, 2016, at 2:10 PM, Christopher Kornher via swift-evolution <swift-evolution@swift.org> wrote:

I may be too late for Swift 3, but I am planning to propose changes to the default behavior for closures capturing object references. The introduction of Swift Playgrounds has raised the importance of simplifying the coding of leak-free, crash-free closures. New developers should not have to understand closure memory management to start writing useful and correct code.

The topic of the closure weak/strong dance has been discussed on this list before. This proposal differs from previous proposals in that it will eliminate the dance altogether by default. I am very interested in hearing others’ opinions as to whether the benefits outweigh the costs of various options.

I have found that Swift’s capture lists and rules are a bit of a mystery to many experienced developers, even though Swift’s closure capture rules are very similar to those of Objective-C. Capture lists are probably thought of as opaque incantations by many new Swift developers if they are aware of them at all. Capture lists should, ideally, not be needed for sensible and safe default behavior.

This discussion is iOS / OS X centric and uses terms from those domains, but these issues should be applicable to almost any codebase that uses closures capturing object references.

Capture lists are required by the fact that object references are captured as `strong` by default, often leading to strong reference cycles and memory leaks.

Use of ‘unowned’
————————
Many examples of using closures capture self as `unowned`. Often, this pattern does not scale well beyond simple examples. iOS and MacOS applications with dynamic UIs, for example, switch between numerous views and view controllers. These objects are dereferenced and reclaimed when they are no longer needed. Closures capturing these objects as `unowned` crash when the references are accessed.

Unfortunately, ‘unowned’ captures are tempting because they eliminate `guard` and `if let` constructs and avoid littering code with optional unwrapping. They are also slightly more performant, but this difference is probably negligible in most application code.

Capturing escaping object references as `unowned` is only safe when object lifetimes are perfectly understood. In complex systems, it is difficult to predict execution order. Even if object lifetimes are perfectly understood when code is originally written, seemingly innocuous changes to complex systems can negate original assumptions.

For these reasons, I believe that capturing object references as `unowned` should be considered an advanced optimization technique.

I now routinely create closures that capture `self` and other object references as ‘weak’ even if I think that I feel that `unowned ` would be safe. This may not be the absolutely most performant solution, but it is straightforward and robust.

The core proposal:
——————

Closures capturing object references should automatically capture all object references as weak.

The closure defined in:

class ClosureOwner2 {
    var aClosure: (() -> Void)?
   
    func aMethod() {
        aClosure = { [weak self] in
           self?.someOtherMethod()
       }
    }

    func someOtherMethod(){}
}

would normally be written as:

        aClosure = {
           self?.someOtherMethod()
        }

Closures that can be optimized to safely capture object references as `unowned` can use the current syntax.

Swift 2 closure without explicit capture lists for object references will not compile.

Capturing strong object references can be very useful in certain circumstances and have a straightforward syntax:

        aClosure = { [strong self] in
           self.someOtherMethod()
        }

Alternatives / Modifications / Improvements(?):
—————————————————————

1) Closures with object references could be simplified further by implicitly including ‘let’ guards for all object references:

        aClosure = {
           // This is included implicitly at the top of the closure:
  // guard let strongSelf = self else { return }

            /*strongSelf*/ self.someOtherMethod()
  print( “This will not appear when self is nil.” )

  … other uses of strongSelf…

        }

This would have the effect of making the execution of the closure dependent upon the successful unwrapping of all of its object references. Object references that are not required to unwrap successfully can be captured as `weak’ if desired.

2) Some of the magic in #1 could be eliminated by introducing a new capture type: ‘required’ to specify ‘weak guarded’ captures, allowing the example closure to be written:

        aClosure = { [required self] in
           self.someOtherMethod()
	print( “This will not appear when self is nil.” )
	print( “This is not the default behavior and the reason for this is clearly stated.” ) 
        }

This reduces the amount of code required, but may increase the cognitive burden over using the current syntax.

`required` is called `guard` in the “Simplified notation” proposal: https://gist.github.com/emaloney/d34ac9b134ece7c60440

This proposal will differ from the “Simplified notation” proposal in that all object references would be captured as `weak` by default in all closures.

Summary
—————-
Closures with objects references occur frequently in Swift code and the use of closures is probably only going to increase. Current capture list rules are not developer friendly and force developers to deal with subtle asynchronous memory management issues. This increases the cognitive burden particularly for new developers.

Closures should be safe and straightforward by default.

Enhancement #1 without #2 would probably be the easiest option for new developers and result the smallest code size. The question is: is it too “magical” and are the changes in execution behavior going to be significant in practice?

The rules for closure object references with enhancement #1 can be stated succinctly:

By default:

Closures do not affect reference counts of the objects that they reference.
All object references used within a closure must unwrap successfully for the closure to execute.

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charlie Monroe) #4

All object references used within a closure must unwrap successfully for the closure to execute.

I agree with the logic of this proposal, but this is the confusing part or a part that I slightly disagree with.

By this logic, the block won't be invoked if all captured variables can't be unwrapped, which is definitely confusing to the newcomer (to whom this is partially targetted as you've mentioned) if he puts a breakpoint in it and doesn't get executed even when he's explicitely invoking it somewhere.

On the other hand, when it crashes, it gives him some idea that something's wrong.

···

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Manuel Krebber) #5

The core proposal:
——————

Closures capturing object references should automatically capture all
object references as weak.

In my code, most closures are used in a functional programming capacity,
e.g. with map(), reduce, etc. Hence, most closures are non-escaping and
local, where strong capture is the desired way. Otherwise I would have
to litter everything with optional unwrapping or add the explicit
capture definition which would both make the code less readable in my
opinion.

1) Closures with object references could be simplified further by
implicitly including ‘let’ guards for all object references:

This sounds good for closures without return value, but how would you
handle closures with non-optional non-void return values?
Also I think that explicit error handling in case of "expired" objects
is safer than silent failure (by not executing the closure).

Kind regards, Manuel

···

On 06/26/2016 09:10 PM, Christopher Kornher via swift-evolution wrote:


(Christopher Kornher) #6

I may be too late for Swift 3, but I am planning to propose changes to the default behavior for closures capturing object references. The introduction of Swift Playgrounds has raised the importance of simplifying the coding of leak-free, crash-free closures. New developers should not have to understand closure memory management to start writing useful and correct code.

The topic of the closure weak/strong dance has been discussed on this list before. This proposal differs from previous proposals in that it will eliminate the dance altogether by default. I am very interested in hearing others’ opinions as to whether the benefits outweigh the costs of various options.

The problem is that strong reference capture is probably the far more common case.

Strong reference capture has not been more common in carefully written code in my experience. Swift is starting to be used for many different problem domains, so your experience may be different. Any examples of real-world code would be greatly appreciated.

Sometimes closures contain the only references to objects, but this is not common in code that I have seen. It would be extremely rare for strong references to be required for all the references in closure with multiple references (the current default behavior). Long closures can reference many objects and it is all too easy to leave a reference out of the capture list and create a reference cycle. I believe that this pattern should be called-out (see below).

Strong references are occasionally needed to ensure operations are performed when objects would otherwise be reclaimed, but I have not seen many of these cases. This pattern could be more common in other frameworks. I believe that this pattern should be called-out (see below).

Multiple capture rules for closures can be supported if desired. The ```[required…``` capture qualifier in the original email is one way todo this. The question mark could be used in a way analogous to `try?` to identify closures using the proposed rules:

let a:()->Void = {…}?
or
let a:()->Void = ?{…}
etc.

This would obviously add more complexity but would still be an improvement, I believe.

If you wanted to say that @noescape closures capture references strongly by default, while escaping closures capture them weakly by default that may be closer to reasonable for most situations but now you have magic behavior with an even greater cognitive overhead. (I wonder if it would be more common that @noescape captures strongly, escaping captures self weak by default but other objects strongly… of course that would have even worse cognitive overhead.)

I did consider treating self differently, but this leads to some very strange cases when delegation, factories and other patterns are considered.

This email was implicitly referring to escaping uses of closures. The same closure can be used as escaping or non-escaping. The Swift documentation states:

"Marking a closure with @noescape lets the compiler make more aggressive optimizations because it knows more information about the closure’s lifespan."

@noescape is essentially a hint to the compiler. Optimizers would be free to use strong or unowned references if they can determine that it is safe to do so without altering behavior.

No matter what, without a garbage collector you are stuck with sub-optimal solutions for fixing reference cycles. I could imagine a language feature that automatically detected trivial cycles (A -> B -> A) but anything more complex just becomes a form of garbage collection anyway.

Object networks are difficult enough with ARC without dozens of closures with unnecessary strongly captured references. The new Xcode tools will be a huge help with leaks, but they should not be required.

I don’t think there is a way to square this circle. Either you have one “automagic” behavior that is wrong for some cases (whether strong or weak is the default), or you require everyone to spam their closures with explicit capture annotations even if there’s a default “capture everything strongly” variant (see C++ lambdas).

Yes, we are discussing tradeoffs. Writing correct code with closures will always require care.I believe that is is better to have safe, leak free code by default than not. I also believe that capturing strong references by default can probably lead to at least as many unexpected behaviors as capturing weak references. I don’t think that many developers would expect, for example, to see zombie view controllers that have not been associated with an active view hierarchy for weeks because a closure is holding on to a strong reference.

Use of ‘unowned’
————————

I now routinely create closures that capture `self` and other object references as ‘weak’ even if I think that I feel that `unowned ` would be safe. This may not be the absolutely most performant solution, but it is straightforward and robust.

I agree and our team has adopted the rule that use of unowned is not allowed unless the declaration is private and there is profiler proof that it represents a performance problem, and if used warning comments must be placed in the code. Weak is almost never a performance problem and eliminates the risk of a crash, so it is highly preferable to unowned.

I’d go so far as to say unowned should be removed; let the user use Unmanaged<T> if they need to capture an unowned reference. They’re entering expert territory anyway.

I use ‘unowned' regularly for back pointers but not closures. The documentation should contain more warnings about the use of `unowned` at least.

The core proposal:
——————

Closures capturing object references should automatically capture all object references as weak.

This becomes a form of the Objective-C messages-to-nil-do-nothing problem where you don’t crash but your closure doesn’t do any work (or does half the work!) because the reference(s) are/become nil. It doesn’t save you from reasoning about object lifetime because it is just as easy for the closure to capture the last reference to an object or for the lifetime to differ from the closure lifetime. You’re just trading reference cycles for a different problem.

Yes, this is based upon the opinion that operations on objects that would otherwise have been reclaimed very often can be ignored. If they cannot be ignored, the reference must be captured strongly, but this is relatively rare and should probably be called-out anyway, given the way that developers are forced to automatically invoke the weak/strong dance when creating closures. This suggestion is based upon my experience with thousands(?) of Swift closures in iOS apps in the past two years.

2) Some of the magic in #1 could be eliminated by introducing a new capture type: ‘required’ to specify ‘weak guarded’ captures, allowing the example closure to be written:

This has been debated before. I support the idea of a “required” capture specifier but IIRC the core team was not supportive of the idea because it adds another layer of magic on the existing magic (object deallocated magically means your closure never executes).

The increase in complexity may not be worth the elimination of guard statements. This proposal is different in that it retains a single capture default for closures. The ‘required’ keyword does not change the way references are captured. This may be a significant difference or not.

···

On Jun 26, 2016, at 4:25 PM, Russ Bishop <xenadu@gmail.com> wrote:

On Jun 26, 2016, at 12:10 PM, Christopher Kornher via swift-evolution <swift-evolution@swift.org> wrote:

Russ


(Christopher Kornher) #7

The core proposal:
——————

Closures capturing object references should automatically capture all
object references as weak.

In my code, most closures are used in a functional programming capacity,
e.g. with map(), reduce, etc. Hence, most closures are non-escaping and
local, where strong capture is the desired way. Otherwise I would have
to litter everything with optional unwrapping or add the explicit
capture definition which would both make the code less readable in my
opinion.

I thought about this some more and it makes sense to treat non-escaping closures are they are treated now. This might increase the burden on the compiler especially because these closures may not be declared inline. This would be far more straightforward than having to worry about changes to object existence within one or more invocations of a @nonescaping closure, especially in multi-threaded code.

I do not think that this would be a significant change for developers in practice. Any developer who would try to rely upon object references changing within the application of a @nonescaping closure would probably have read the manual very carefully :slight_smile:

1) Closures with object references could be simplified further by
implicitly including ‘let’ guards for all object references:

This sounds good for closures without return value, but how would you
handle closures with non-optional non-void return values?

Good point.

1) The simplest solution would be to have these closures require capture lists to some or all object references. Given the prevalence of the weak/strong dance, calling these out in some way probably should be done anyway.

2) Another option would be to eliminate the implicit nil check guards, making all object references optional. The compiler would force these to be unwrapped, so users would be guided to do the right thing.

3) Just always weakly capturing all object references would treat all closures uniformly.

Also I think that explicit error handling in case of "expired" objects
is safer than silent failure (by not executing the closure).

I believe that it is decision that should be left up to the author of the closure. Sometimes it is, sometimes it isn’t.

···

On Jun 27, 2016, at 2:45 AM, Manuel Krebber via swift-evolution <swift-evolution@swift.org> wrote:
On 06/26/2016 09:10 PM, Christopher Kornher via swift-evolution wrote:

Kind regards, Manuel

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Christopher Kornher) #8

All object references used within a closure must unwrap successfully for the closure to execute.

I agree with the logic of this proposal, but this is the confusing part or a part that I slightly disagree with.

By this logic, the block won't be invoked if all captured variables can't be unwrapped, which is definitely confusing to the newcomer (to whom this is partially targetted as you've mentioned) if he puts a breakpoint in it and doesn't get executed even when he's explicitely invoking it somewhere.

On the other hand, when it crashes, it gives him some idea that something's wrong.

Tooling could alleviate some of this mystery. For example:

1) In Xcode and other GUIs, closures that will not be executed could be greyed-out, perhaps with the reason overlaid on the closure perhaps this would only happen when a breakpoint was set within the closure. Perhaps the app could break on the on breakpoints within non-executing closures and notify the user that the closure did not execute.

2) Debugger console commands could be added to describe the execution state of closures in scope and closure variables.

3) Debug apps could bottleneck all closures through a function that that can be break-pointed. Breakpoints could be edited to filter on specific closures.

4) Some runtime switches could be added to enable verbose login modes for closures.

I don’t think that this is an insurmountable problem. There are many features of modern applications that are difficult to debug.

···

On Jun 26, 2016, at 11:22 PM, Charlie Monroe via swift-evolution <swift-evolution@swift.org> wrote:

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Russ Bishop) #9

I may be too late for Swift 3, but I am planning to propose changes to the default behavior for closures capturing object references. The introduction of Swift Playgrounds has raised the importance of simplifying the coding of leak-free, crash-free closures. New developers should not have to understand closure memory management to start writing useful and correct code.

The topic of the closure weak/strong dance has been discussed on this list before. This proposal differs from previous proposals in that it will eliminate the dance altogether by default. I am very interested in hearing others’ opinions as to whether the benefits outweigh the costs of various options.

The problem is that strong reference capture is probably the far more common case.

Strong reference capture has not been more common in carefully written code in my experience. Swift is starting to be used for many different problem domains, so your experience may be different. Any examples of real-world code would be greatly appreciated.

In my experience reference cycles are almost always caused by capturing self strongly.

Sometimes closures contain the only references to objects, but this is not common in code that I have seen. It would be extremely rare for strong references to be required for all the references in closure with multiple references (the current default behavior). Long closures can reference many objects and it is all too easy to leave a reference out of the capture list and create a reference cycle. I believe that this pattern should be called-out (see below).

Strong references are occasionally needed to ensure operations are performed when objects would otherwise be reclaimed, but I have not seen many of these cases. This pattern could be more common in other frameworks. I believe that this pattern should be called-out (see below).

Multiple capture rules for closures can be supported if desired. The ```[required…``` capture qualifier in the original email is one way todo this. The question mark could be used in a way analogous to `try?` to identify closures using the proposed rules:

let a:()->Void = {…}?
or
let a:()->Void = ?{…}
etc.

This would obviously add more complexity but would still be an improvement, I believe.

Possibly, though this inevitably ends up with the C++ lambda rules more or less. To put it another way, why would Swift introduce a new way of handling this instead of just requiring all closures to specify whether they want strong default, weak default, or whatever and completely eliminate any automatic behavior? Unfortunately that makes creating closures much more annoying.

If you wanted to say that @noescape closures capture references strongly by default, while escaping closures capture them weakly by default that may be closer to reasonable for most situations but now you have magic behavior with an even greater cognitive overhead. (I wonder if it would be more common that @noescape captures strongly, escaping captures self weak by default but other objects strongly… of course that would have even worse cognitive overhead.)

I did consider treating self differently, but this leads to some very strange cases when delegation, factories and other patterns are considered.

This email was implicitly referring to escaping uses of closures. The same closure can be used as escaping or non-escaping. The Swift documentation states:

"Marking a closure with @noescape lets the compiler make more aggressive optimizations because it knows more information about the closure’s lifespan."

@noescape is essentially a hint to the compiler. Optimizers would be free to use strong or unowned references if they can determine that it is safe to do so without altering behavior.

The majority of all closures are passed to non-escaping functional methods like map, filter, etc. I don’t see how having them default to weak is a good thing in any capacity. Once we agree on that anything else but strong capture by default will by necessity introduce a cognitive burden.

I don’t think there is a way to square this circle. Either you have one “automagic” behavior that is wrong for some cases (whether strong or weak is the default), or you require everyone to spam their closures with explicit capture annotations even if there’s a default “capture everything strongly” variant (see C++ lambdas).

Yes, we are discussing tradeoffs. Writing correct code with closures will always require care.I believe that is is better to have safe, leak free code by default than not. I also believe that capturing strong references by default can probably lead to at least as many unexpected behaviors as capturing weak references. I don’t think that many developers would expect, for example, to see zombie view controllers that have not been associated with an active view hierarchy for weeks because a closure is holding on to a strong reference.

I don’t agree that weak by default would equal “safe, leak free”. It might be leak-free but it certainly isn’t necessarily safe. In the face of concurrency you can’t overlook the “half executed” situation of consecutively doing “self?.xyz(); self?.abc()”. Most programmers will assume both xyz() and abc() execute or neither execute but that simply isn’t the case. By making weak the default capture this existing problem is greatly exacerbated.

Russ

···

On Jun 27, 2016, at 12:35 AM, Christopher Kornher <ckornher@me.com> wrote:

On Jun 26, 2016, at 4:25 PM, Russ Bishop <xenadu@gmail.com <mailto:xenadu@gmail.com>> wrote:

On Jun 26, 2016, at 12:10 PM, Christopher Kornher via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:


(Dennis Lysenko) #10

My 2c:

This proposal is made more appealing to me because it is not simply a
'beginners will get confused' issue.

I have written tens of thousands of lines of Swift from Swift 1 to the
Swift 3 preview and I still can't shake occasionally accidentally capturing
`self` strongly when I, for example, assign a closure as a listener/action
to a UI component.

To spin off of Christopher's last email, my proposal is thus: we could
include the original proposal (without numbered addendums) and use tooling,
but have it alleviate the burden another way: have the tooling insert
`[strong self]` by default when autocompleting a block.

BTW, @Manuel Krebber: this proposal would not affect your run-of-the-mill
map statement. array.map { object in object.property } requires no explicit
capture since object is intuitively strongly captured there.

What do we think? Worth discussing?

···

On Mon, Jun 27, 2016 at 12:10 PM Christopher Kornher via swift-evolution < swift-evolution@swift.org> wrote:

On Jun 26, 2016, at 11:22 PM, Charlie Monroe via swift-evolution < > swift-evolution@swift.org> wrote:

   All object references used within a closure must unwrap successfully
   for the closure to execute.

I agree with the logic of this proposal, but this is the confusing part or
a part that I slightly disagree with.

By this logic, the block won't be invoked if all captured variables can't
be unwrapped, which is definitely confusing to the newcomer (to whom this
is partially targetted as you've mentioned) if he puts a breakpoint in it
and doesn't get executed even when he's explicitely invoking it somewhere.

On the other hand, when it crashes, it gives him some idea that
something's wrong.

Tooling could alleviate some of this mystery. For example:

1) In Xcode and other GUIs, closures that will not be executed could be
greyed-out, perhaps with the reason overlaid on the closure perhaps this
would only happen when a breakpoint was set within the closure. Perhaps the
app could break on the on breakpoints within non-executing closures and
notify the user that the closure did not execute.

2) Debugger console commands could be added to describe the execution
state of closures in scope and closure variables.

3) Debug apps could bottleneck all closures through a function that that
can be break-pointed. Breakpoints could be edited to filter on specific
closures.

4) Some runtime switches could be added to enable verbose login modes for
closures.

I don’t think that this is an insurmountable problem. There are many
features of modern applications that are difficult to debug.

I believe that these are safe, sensible and understandable rules that will
eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Charlie Monroe) #11

All object references used within a closure must unwrap successfully for the closure to execute.

I agree with the logic of this proposal, but this is the confusing part or a part that I slightly disagree with.

By this logic, the block won't be invoked if all captured variables can't be unwrapped, which is definitely confusing to the newcomer (to whom this is partially targetted as you've mentioned) if he puts a breakpoint in it and doesn't get executed even when he's explicitely invoking it somewhere.

On the other hand, when it crashes, it gives him some idea that something's wrong.

Tooling could alleviate some of this mystery. For example:

1) In Xcode and other GUIs, closures that will not be executed could be greyed-out, perhaps with the reason overlaid on the closure perhaps this would only happen when a breakpoint was set within the closure. Perhaps the app could break on the on breakpoints within non-executing closures and notify the user that the closure did not execute.

2) Debugger console commands could be added to describe the execution state of closures in scope and closure variables.

3) Debug apps could bottleneck all closures through a function that that can be break-pointed. Breakpoints could be edited to filter on specific closures.

4) Some runtime switches could be added to enable verbose login modes for closures.

This can solve issues that you may have with a 2KLOC project that does nothing but waits for the user to click on something. When you have a project of 100KLOC, running at 100% CPU on a background thread, I don't think you'd debug anything since console would be flooded by thousands of log messages...

···

On Jun 27, 2016, at 9:10 PM, Christopher Kornher <ckornher@me.com> wrote:

On Jun 26, 2016, at 11:22 PM, Charlie Monroe via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I don’t think that this is an insurmountable problem. There are many features of modern applications that are difficult to debug.

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(Manuel Krebber) #12

The core proposal:
——————

Closures capturing object references should automatically capture all
object references as weak.

In my code, most closures are used in a functional programming capacity,
e.g. with map(), reduce, etc. Hence, most closures are non-escaping and
local, where strong capture is the desired way. Otherwise I would have
to litter everything with optional unwrapping or add the explicit
capture definition which would both make the code less readable in my
opinion.

I thought about this some more and it makes sense to treat
non-escaping closures are they are treated now. This might increase
the burden on the compiler especially because these closures may not
be declared inline. This would be far more straightforward than having
to worry about changes to object existence within one or more
invocations of a @nonescaping closure, especially in multi-threaded
code.

I do not think that this would be a significant change for developers
in practice. Any developer who would try to rely upon object
references changing within the application of a @nonescaping closure
would probably have read the manual very carefully :slight_smile:

I am unsure whether treating closures differently depending on a @nonescaping attribute on a function is a good idea.
The problem is that, theoretically, the closure might be stored in a variable or used with different functions. Then how do you decide whether the default is strong or weak capture?

Example:

func f1(_ f : (Int) -> Int) {
...
}

func f2(_ f : @noescape (Int) -> Int) {
...
}

let foo = Foo()
let f : (Int) -> Int = {
   return foo.x
}

f1(f)
f2(f)

In addition different default capture behaviour dependant on context could be confusing for developers I think.

1) Closures with object references could be simplified further by
implicitly including ‘let’ guards for all object references:

This sounds good for closures without return value, but how would you
handle closures with non-optional non-void return values?

Good point.

1) The simplest solution would be to have these closures require
capture lists to some or all object references. Given the prevalence
of the weak/strong dance, calling these out in some way probably
should be done anyway.

2) Another option would be to eliminate the implicit nil check guards,
making all object references optional. The compiler would force these
to be unwrapped, so users would be guided to do the right thing.

3) Just always weakly capturing all object references would treat all
closures uniformly.

I think I favour 2) out of these, even though it still means the code will potentially be littered with ?. or let guards...

Kind regards, Manuel

···

Am 27.06.2016 20:55, schrieb Christopher Kornher:

On Jun 27, 2016, at 2:45 AM, Manuel Krebber via swift-evolution >> <swift-evolution@swift.org> wrote:
On 06/26/2016 09:10 PM, Christopher Kornher via swift-evolution wrote:


(Christopher Kornher) #13

I may be too late for Swift 3, but I am planning to propose changes to the default behavior for closures capturing object references. The introduction of Swift Playgrounds has raised the importance of simplifying the coding of leak-free, crash-free closures. New developers should not have to understand closure memory management to start writing useful and correct code.

The topic of the closure weak/strong dance has been discussed on this list before. This proposal differs from previous proposals in that it will eliminate the dance altogether by default. I am very interested in hearing others’ opinions as to whether the benefits outweigh the costs of various options.

The problem is that strong reference capture is probably the far more common case.

Strong reference capture has not been more common in carefully written code in my experience. Swift is starting to be used for many different problem domains, so your experience may be different. Any examples of real-world code would be greatly appreciated.

In my experience reference cycles are almost always caused by capturing self strongly.

Sometimes closures contain the only references to objects, but this is not common in code that I have seen. It would be extremely rare for strong references to be required for all the references in closure with multiple references (the current default behavior). Long closures can reference many objects and it is all too easy to leave a reference out of the capture list and create a reference cycle. I believe that this pattern should be called-out (see below).

Strong references are occasionally needed to ensure operations are performed when objects would otherwise be reclaimed, but I have not seen many of these cases. This pattern could be more common in other frameworks. I believe that this pattern should be called-out (see below).

Multiple capture rules for closures can be supported if desired. The ```[required…``` capture qualifier in the original email is one way todo this. The question mark could be used in a way analogous to `try?` to identify closures using the proposed rules:

let a:()->Void = {…}?
or
let a:()->Void = ?{…}
etc.

This would obviously add more complexity but would still be an improvement, I believe.

Possibly, though this inevitably ends up with the C++ lambda rules more or less. To put it another way, why would Swift introduce a new way of handling this instead of just requiring all closures to specify whether they want strong default, weak default, or whatever and completely eliminate any automatic behavior? Unfortunately that makes creating closures much more annoying.

I took a quick look at C++ capture lists. Yeah, we don’t want those. But another default behavior for captures does make sense for Swift I think. More to come.

If you wanted to say that @noescape closures capture references strongly by default, while escaping closures capture them weakly by default that may be closer to reasonable for most situations but now you have magic behavior with an even greater cognitive overhead. (I wonder if it would be more common that @noescape captures strongly, escaping captures self weak by default but other objects strongly… of course that would have even worse cognitive overhead.)

I did consider treating self differently, but this leads to some very strange cases when delegation, factories and other patterns are considered.

This email was implicitly referring to escaping uses of closures. The same closure can be used as escaping or non-escaping. The Swift documentation states:

"Marking a closure with @noescape lets the compiler make more aggressive optimizations because it knows more information about the closure’s lifespan."

@noescape is essentially a hint to the compiler. Optimizers would be free to use strong or unowned references if they can determine that it is safe to do so without altering behavior.

The majority of all closures are passed to non-escaping functional methods like map, filter, etc.

Not in the iOS programming that I do most of the time, but I am sure that the is often the case for other types of development. I rarely use object references in @noescape closures, but styles differ.

I don’t see how having them default to weak is a good thing in any capacity.

After thinking about it, i agree. @noescape usages of closures should capture strongly.

Once we agree on that anything else but strong capture by default will by necessity introduce a cognitive burden.

When closures are used to define UI behaviors, capturing weak is a very reasonable default. When GUI elements go away, the reason for the behaviors usually goes with them. This is the context that inspired this.

···

On Jun 27, 2016, at 2:51 PM, Russ Bishop via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 27, 2016, at 12:35 AM, Christopher Kornher <ckornher@me.com <mailto:ckornher@me.com>> wrote:

On Jun 26, 2016, at 4:25 PM, Russ Bishop <xenadu@gmail.com <mailto:xenadu@gmail.com>> wrote:

On Jun 26, 2016, at 12:10 PM, Christopher Kornher via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I don’t think there is a way to square this circle. Either you have one “automagic” behavior that is wrong for some cases (whether strong or weak is the default), or you require everyone to spam their closures with explicit capture annotations even if there’s a default “capture everything strongly” variant (see C++ lambdas).

Yes, we are discussing tradeoffs. Writing correct code with closures will always require care.I believe that is is better to have safe, leak free code by default than not. I also believe that capturing strong references by default can probably lead to at least as many unexpected behaviors as capturing weak references. I don’t think that many developers would expect, for example, to see zombie view controllers that have not been associated with an active view hierarchy for weeks because a closure is holding on to a strong reference.

I don’t agree that weak by default would equal “safe, leak free”. It might be leak-free but it certainly isn’t necessarily safe. In the face of concurrency you can’t overlook the “half executed” situation of consecutively doing “self?.xyz(); self?.abc()”. Most programmers will assume both xyz() and abc() execute or neither execute but that simply isn’t the case. By making weak the default capture this existing problem is greatly exacerbated.

Russ_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution


(L Mihalkovic) #14

Regards
(From mobile)

My 2c:

This proposal is made more appealing to me because it is not simply a 'beginners will get confused' issue.

I have written tens of thousands of lines of Swift from Swift 1 to the Swift 3 preview and I still can't shake occasionally accidentally capturing `self` strongly when I, for example, assign a closure as a listener/action to a UI component.

The problem of is that it is easier for an app to survive an accidental extra capture than it is for it the survive a missing one.. So if this is really for beginners, then it is much better to keep the current behavior which will lead them to have leaky apps, than it is to give them apps whre objects will vanish from under their feet... granted neither is good to begin with.

···

On Jun 27, 2016, at 10:18 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org> wrote:

To spin off of Christopher's last email, my proposal is thus: we could include the original proposal (without numbered addendums) and use tooling, but have it alleviate the burden another way: have the tooling insert `[strong self]` by default when autocompleting a block.

BTW, @Manuel Krebber: this proposal would not affect your run-of-the-mill map statement. array.map { object in object.property } requires no explicit capture since object is intuitively strongly captured there.

What do we think? Worth discussing?

On Mon, Jun 27, 2016 at 12:10 PM Christopher Kornher via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 26, 2016, at 11:22 PM, Charlie Monroe via swift-evolution <swift-evolution@swift.org> wrote:

All object references used within a closure must unwrap successfully for the closure to execute.

I agree with the logic of this proposal, but this is the confusing part or a part that I slightly disagree with.

By this logic, the block won't be invoked if all captured variables can't be unwrapped, which is definitely confusing to the newcomer (to whom this is partially targetted as you've mentioned) if he puts a breakpoint in it and doesn't get executed even when he's explicitely invoking it somewhere.

On the other hand, when it crashes, it gives him some idea that something's wrong.

Tooling could alleviate some of this mystery. For example:

1) In Xcode and other GUIs, closures that will not be executed could be greyed-out, perhaps with the reason overlaid on the closure perhaps this would only happen when a breakpoint was set within the closure. Perhaps the app could break on the on breakpoints within non-executing closures and notify the user that the closure did not execute.

2) Debugger console commands could be added to describe the execution state of closures in scope and closure variables.

3) Debug apps could bottleneck all closures through a function that that can be break-pointed. Breakpoints could be edited to filter on specific closures.

4) Some runtime switches could be added to enable verbose login modes for closures.

I don’t think that this is an insurmountable problem. There are many features of modern applications that are difficult to debug.

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Christopher Kornher) #15

The core proposal:
——————
Closures capturing object references should automatically capture all
object references as weak.

In my code, most closures are used in a functional programming capacity,
e.g. with map(), reduce, etc. Hence, most closures are non-escaping and
local, where strong capture is the desired way. Otherwise I would have
to litter everything with optional unwrapping or add the explicit
capture definition which would both make the code less readable in my
opinion.

I thought about this some more and it makes sense to treat
non-escaping closures are they are treated now. This might increase
the burden on the compiler especially because these closures may not
be declared inline. This would be far more straightforward than having
to worry about changes to object existence within one or more
invocations of a @nonescaping closure, especially in multi-threaded
code.
I do not think that this would be a significant change for developers
in practice. Any developer who would try to rely upon object
references changing within the application of a @nonescaping closure
would probably have read the manual very carefully :slight_smile:

I am unsure whether treating closures differently depending on a @nonescaping attribute on a function is a good idea.
The problem is that, theoretically, the closure might be stored in a variable or used with different functions. Then how do you decide whether the default is strong or weak capture?

The compiler/runtime (in theory at least) knows how a closure is going to be applied. I am not a compiler writer, but two versions of the closure could be created when necessary and the appropriate one could be used depending upon the context.

Example:

func f1(_ f : (Int) -> Int) {
...
}

func f2(_ f : @noescape (Int) -> Int) {
...
}

let foo = Foo()
let f : (Int) -> Int = {
return foo.x
}

f1(f)
f2(f)

In addition different default capture behaviour dependant on context could be confusing for developers I think.

The changes in behavior in the application of @noescaping closures are very much] edge case and are as likely to result from actions in other threads as anything else. So Capturing references as strong by default makes @noescaping closures more predictable and eliminates the potential for really some really subtle bug and race conditions.

For the same reason, implicit “let guards” in escaping closures should be applied early to improve predicability. Developers wanting to modify reference counts within closures are free to use traditional capture lists and capture and un-capture references as needed.

Memory management within closures should not be the default behavior, but it should be possible and discoverable.

···

On Jun 28, 2016, at 6:53 AM, admin@wheerd.de wrote:
Am 27.06.2016 20:55, schrieb Christopher Kornher:

On Jun 27, 2016, at 2:45 AM, Manuel Krebber via swift-evolution <swift-evolution@swift.org> wrote:
On 06/26/2016 09:10 PM, Christopher Kornher via swift-evolution wrote:

1) Closures with object references could be simplified further by
implicitly including ‘let’ guards for all object references:

This sounds good for closures without return value, but how would you
handle closures with non-optional non-void return values?

Good point.
1) The simplest solution would be to have these closures require
capture lists to some or all object references. Given the prevalence
of the weak/strong dance, calling these out in some way probably
should be done anyway.
2) Another option would be to eliminate the implicit nil check guards,
making all object references optional. The compiler would force these
to be unwrapped, so users would be guided to do the right thing.
3) Just always weakly capturing all object references would treat all
closures uniformly.

I think I favour 2) out of these, even though it still means the code will potentially be littered with ?. or let guards...

Kind regards, Manuel


(Christopher Kornher) #16

All object references used within a closure must unwrap successfully for the closure to execute.

I agree with the logic of this proposal, but this is the confusing part or a part that I slightly disagree with.

By this logic, the block won't be invoked if all captured variables can't be unwrapped, which is definitely confusing to the newcomer (to whom this is partially targetted as you've mentioned) if he puts a breakpoint in it and doesn't get executed even when he's explicitely invoking it somewhere.

On the other hand, when it crashes, it gives him some idea that something's wrong.

Tooling could alleviate some of this mystery. For example:

1) In Xcode and other GUIs, closures that will not be executed could be greyed-out, perhaps with the reason overlaid on the closure perhaps this would only happen when a breakpoint was set within the closure. Perhaps the app could break on the on breakpoints within non-executing closures and notify the user that the closure did not execute.

2) Debugger console commands could be added to describe the execution state of closures in scope and closure variables.

3) Debug apps could bottleneck all closures through a function that that can be break-pointed. Breakpoints could be edited to filter on specific closures.

4) Some runtime switches could be added to enable verbose login modes for closures.

This can solve issues that you may have with a 2KLOC project that does nothing but waits for the user to click on something. When you have a project of 100KLOC, running at 100% CPU on a background thread, I don't think you'd debug anything since console would be flooded by thousands of log messages…

This technique is used with CoreData, OS X Bindings, and probably other frameworks and yes, it is often a tool of last resort. Tools like grep and spunk are often needed to make use of the output, but this is an effective technique, if not a very efficient one. I much prefer interactive debugger support. One advantage of global logging options is that they can be to used over long periods, on remote machines to capture infrequent errors.

···

On Jun 27, 2016, at 11:52 PM, Charlie Monroe via swift-evolution <swift-evolution@swift.org> wrote:

On Jun 27, 2016, at 9:10 PM, Christopher Kornher <ckornher@me.com <mailto:ckornher@me.com>> wrote:

On Jun 26, 2016, at 11:22 PM, Charlie Monroe via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

I don’t think that this is an insurmountable problem. There are many features of modern applications that are difficult to debug.

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Christopher Kornher) #17

Regards
(From mobile)

My 2c:

This proposal is made more appealing to me because it is not simply a 'beginners will get confused' issue.

I have written tens of thousands of lines of Swift from Swift 1 to the Swift 3 preview and I still can't shake occasionally accidentally capturing `self` strongly when I, for example, assign a closure as a listener/action to a UI component.

The problem of is that it is easier for an app to survive an accidental extra capture than it is for it the survive a missing one.. So if this is really for beginners, then it is much better to keep the current behavior which will lead them to have leaky apps, than it is to give them apps whre objects will vanish from under their feet... granted neither is good to begin with.

For many cases, that is exactly the behavior you want. In my experience, the instances of closures being the only live reference to objects not being constructed by the closure are rare.

To clarify, the proposed implicit guard statements would create strong references once the closure starts executing, so referenced objects would not disappear during the execution of the closure.

···

On Jun 27, 2016, at 2:35 PM, L. Mihalkovic via swift-evolution <swift-evolution@swift.org> wrote:
On Jun 27, 2016, at 10:18 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

To spin off of Christopher's last email, my proposal is thus: we could include the original proposal (without numbered addendums) and use tooling, but have it alleviate the burden another way: have the tooling insert `[strong self]` by default when autocompleting a block.

BTW, @Manuel Krebber: this proposal would not affect your run-of-the-mill map statement. array.map { object in object.property } requires no explicit capture since object is intuitively strongly captured there.

What do we think? Worth discussing?

On Mon, Jun 27, 2016 at 12:10 PM Christopher Kornher via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jun 26, 2016, at 11:22 PM, Charlie Monroe via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

All object references used within a closure must unwrap successfully for the closure to execute.

I agree with the logic of this proposal, but this is the confusing part or a part that I slightly disagree with.

By this logic, the block won't be invoked if all captured variables can't be unwrapped, which is definitely confusing to the newcomer (to whom this is partially targetted as you've mentioned) if he puts a breakpoint in it and doesn't get executed even when he's explicitely invoking it somewhere.

On the other hand, when it crashes, it gives him some idea that something's wrong.

Tooling could alleviate some of this mystery. For example:

1) In Xcode and other GUIs, closures that will not be executed could be greyed-out, perhaps with the reason overlaid on the closure perhaps this would only happen when a breakpoint was set within the closure. Perhaps the app could break on the on breakpoints within non-executing closures and notify the user that the closure did not execute.

2) Debugger console commands could be added to describe the execution state of closures in scope and closure variables.

3) Debug apps could bottleneck all closures through a function that that can be break-pointed. Breakpoints could be edited to filter on specific closures.

4) Some runtime switches could be added to enable verbose login modes for closures.

I don’t think that this is an insurmountable problem. There are many features of modern applications that are difficult to debug.

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution


(Christopher Kornher) #18

Regards
(From mobile)

My 2c:

This proposal is made more appealing to me because it is not simply a 'beginners will get confused' issue.

I have written tens of thousands of lines of Swift from Swift 1 to the Swift 3 preview and I still can't shake occasionally accidentally capturing `self` strongly when I, for example, assign a closure as a listener/action to a UI component.

The problem of is that it is easier for an app to survive an accidental extra capture than it is for it the survive a missing one.. So if this is really for beginners, then it is much better to keep the current behavior which will lead them to have leaky apps, than it is to give them apps whre objects will vanish from under their feet... granted neither is good to begin with.

Not always. large memory leaks are often fatal in iOS because of the limited memory footprint. On more constrained devices the problem would e worse.

All of the great feedback has made me realize that my original proposal was more UIKit centric that I thought. One size does not fit all. Any errors in closure memory management can be fatal and the challenge is to optimize the experience for all programming domains and styles.

···

On Jun 27, 2016, at 2:35 PM, L. Mihalkovic via swift-evolution <swift-evolution@swift.org> wrote:
On Jun 27, 2016, at 10:18 PM, Dennis Lysenko via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

To spin off of Christopher's last email, my proposal is thus: we could include the original proposal (without numbered addendums) and use tooling, but have it alleviate the burden another way: have the tooling insert `[strong self]` by default when autocompleting a block.

BTW, @Manuel Krebber: this proposal would not affect your run-of-the-mill map statement. array.map { object in object.property } requires no explicit capture since object is intuitively strongly captured there.

What do we think? Worth discussing?

On Mon, Jun 27, 2016 at 12:10 PM Christopher Kornher via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Jun 26, 2016, at 11:22 PM, Charlie Monroe via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

All object references used within a closure must unwrap successfully for the closure to execute.

I agree with the logic of this proposal, but this is the confusing part or a part that I slightly disagree with.

By this logic, the block won't be invoked if all captured variables can't be unwrapped, which is definitely confusing to the newcomer (to whom this is partially targetted as you've mentioned) if he puts a breakpoint in it and doesn't get executed even when he's explicitely invoking it somewhere.

On the other hand, when it crashes, it gives him some idea that something's wrong.

Tooling could alleviate some of this mystery. For example:

1) In Xcode and other GUIs, closures that will not be executed could be greyed-out, perhaps with the reason overlaid on the closure perhaps this would only happen when a breakpoint was set within the closure. Perhaps the app could break on the on breakpoints within non-executing closures and notify the user that the closure did not execute.

2) Debugger console commands could be added to describe the execution state of closures in scope and closure variables.

3) Debug apps could bottleneck all closures through a function that that can be break-pointed. Breakpoints could be edited to filter on specific closures.

4) Some runtime switches could be added to enable verbose login modes for closures.

I don’t think that this is an insurmountable problem. There are many features of modern applications that are difficult to debug.

I believe that these are safe, sensible and understandable rules that will eliminate the need for capture lists for many closures. What do you think?

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution