[Draft] Guarded closures and `@guarded` arguments


(Matthew Johnson) #1

I want to thank everyone who commented on the first draft of this proposal. I continue to believe the basic idea is a good one but there were a number of problems with the details of the design presented in that draft. They key insight that led to this new design is the idea of using a sigil at the usage site to establish different semantics than usual for guarded closures. Special thanks to those of you who focused my attention on the call site.

This new draft presents a much stronger, more general design that I hope addresses all of the concerns expressed about the previous draft.

# Guarded Closures and `@guarded` arguments

* Proposal: [SE-NNNN](NNNN-selfsafe.md)
* Authors: [Matthew Johnson](https://github.com/anandabits)
* Review Manager: TBD
* Status: **Awaiting review**

## Introduction

Some APIs have what we might call weak callback semantics. This design is common in many of Apple's newer callback APIs. These APIs greatly reduce the possibility of unintentional reference cycles / leaks by guaranteeing that they do not extend the lifetime of the observation target.

It is possible to implement APIs with this contract in Swift today, but the is don't feel very natural to either library implementers, or more importantly library users. It should be possible to design an API with this important semantic contract that feels natural to use (and implement) in Swift.

This proposal introduces guarded closures (prefixed with a `?` sigil). Closures that use this sigil default to capturing any references as `weak` and guard the invocation to be a no-op if any of the guarded references have been released before it is invoked. If no captured references have been invoked, they are upgraded to strong references for the duration of the invocation. This provides syntactic sugar for a very common pattern in Swift code (sometimes called the weak self / strong self dance).

It also introduces `@guarded` as an annotation that can be used on parameters of function type. Users are required to use a guarded closure when providing an argument for a `@guarded` parameter. This annotation is will be used in cases where it is *usually* (not always) wrong for the argument to extend the lifetime of objects that might be captured by the arguemnt it is given. This is a generalization of weak callback semantics that this proposal calls guarded callback semantics.

An early version of this proposal was discussed in the thread: [`@selfsafe`: a new way to avoid reference cycles](https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20170213/032374.html)

## Motivation

Accidentally forgeting to use weak references is a common problem and can easily lead to reference cycles. Some APIs (such as many kinds of callbacks / notifications / observers) are best designed such that users cannot extend the lifetime of objects captured by the function argument they are given (unless the user explicitly goes out of their way to state their intent to do so).

For example, `UIControl.addTarget(_:action:for:) does not retain the target, thereby preventing users from making the mistake of using a closure with an accidental strong reference. We can do something similar in Swift:

// in the library:
func addTarget<T: AnyObject>(_ target: T, action: T -> Int -> Void) {
  // store a weak reference to the target
  // when the action is fired call ref.map{ action($0)(42) }
}

// in user code:
class C {
  init() {
     addTarget(self, action: C.takesInt)
  }

  func takesInt(_ i: Int) {}
}

Both the library and the caller have to deal with a lot of details and boilerplate that we would prefer to avoid. The natural design in Swift would be to simply take an action function. Unfortunately if we do that we run into a problem:

// in the library
func addAction(_ f: Int -> Void) {
  // store a strong ref to f, which might include a strong ref to a captured self
  // later when the action is fired call f(42)
}

// in user code
class C {
  init() {
     addAction(takesInt)
     // oops! should have been: addAction{ [weak self] self?.takesInt($0) }
  }

  func takesInt(_ i: Int) {}
}

Here the syntax is much nicer, but unfortunately we have unintentionally extended the lifetime of `self`. The burden of ensuring `self` is not captured or is captured weakly falls on users of the library.

It would very nice if it were possible to design an API that has weak or guarded callback semantics while still acheiving the more concise and Swifty syntax.

## Proposed solution

This proposal introduces guarded closures and the `@guarded` annotation for arguments of function type.

### Guarded Closures

A guarded closure can be created in two ways, both of which use the `?` sigil. The `?` sigil was selected because of it's relationship to `Optional` and to optional chaining.

First, the guarded captures are weak references with `Optional` type.

Second, anything following a `?` in an optional chain may or may not be invoked. The same is true of a guarded closure: its contents may or may not be executed when it is called by code that holds a reference to it. If any of the guarded captures have been released it immediately becomes a no-op that returns `()` or `nil`.

#### Bound instance methods

A guarded closure may be created by prefixing a bound instance method reference with the `?` sigil:

let guarded = ?myObject.myMethod

// desugars to:
let guarded = { [weak myObject] in
 guard let myObejct = myObject else {
   return
 }
 myObject.myMethod()
}

#### Inline closures

A guarded closure may be created by prefixing an inline closure with the `?` sigil:

let guarded = ?{ flag in
 flag ? someMethod() : someOtherMethod()
}

// desugars to:
let guarded = { [weak self] flag in
 guard let strongSelf = self else {
   return
 }

 flag ? strongSelf.someMethod() : strongSelf.someOtherMethod()

#### Multiple guarded captures

let viewController = getAViewControllerOwnedElsewhere()
let guarded = ?{ flag in
 flag ? someMethod() : someOtherMethod()
 viewController.doSomething()
}

// desugars to:
let guarded = { [weak self, weak viewController] flag in
 guard let strongSelf = self, viewController = viewController else {
   return
 }

 flag ? strongSelf.someMethod() : strongSelf.someOtherMethod()
 viewController.doSomething()

#### Capture lists

As with any closure, a guarded closure may specify a capture list. Any captures specified in the capture list are not guarded and instead adopt the semantics specified by the capture list. This includes `weak` captures.

let guarded = ?{ [weak object, unowned otherObject, strong thirdObject] in
 aMethodOnSelf()
 object?.doSomething(with: otherObject, and: thirdObject)
}

// desugars to:
let guarded = ?{ [weak self, weak object, unowned otherObject, strong thirdObject] in
 guard let strongSelf = self else {
   return
 }

 strongSelf.aMethodOnSelf()
 object?.doSomething(with: otherObject, and: thirdObject)
}

#### Self reference in escaping guarded closures

Guarded closures do not extend the lifetime of any objects unless a `strong` capture is specified in the capture list. Because this is the case users are not required to explicitly reference self using the `self.` prefix inside a guarded closure.

#### Return values

Guarded closures bail immediately when one of the guarded captures has been released. This proposal handles return values by requiring them to always return `Void` or `Optional`. If the inferred type of the closure is not `Optional` it is wrapped in `Optional`. `Optional` returning guarded closures return `nil` when any of the guarded captures has been released. `Void` returning guarded closures simply become a no-op after the first guarded capture has been released.

### `@guarded`

This proposal introduces the ability for arguments of function type to be annotated with the `@guarded` attribute. When `@guarded` is used the caller must supply a guarded or non-capturing closure as an argument. This ensures that users of the API think very carefully before providing an argument that captures a strong reference, and requires them to explictly state their intent when they wish to do so.

Because `@guarded` only makes sense in the context of an `@escaping` closure it implies `@escaping`. Users do not need to specify both annotations.

This allows a library to do something like the following (a simplified version of a hypothetical future method on `UIControl`):

  class UIControl {
     var action: () -> Void = {}

     func setAction(_ action: @guarded () -> Void) {
        self.action = action
     }

     func fireAction() {
       action()
     }
  }

This API can be used as follows:

myControl.setAction(?myMethod) // great! no reference cycle.

let f = ?myMethod
// ok: compiler knows f is guarded
secondControl.setAction(f)

// otherObject should live as long as the closure might be invoked
myOtherControl.setAction ?{ [strong otherObject] in
    // `self.doSomethingElse()` is not required.
    // The compiler knows we cannot create a reference cycle here.
    doSomethingElse()
    otherObject.doSomething()
}

// ok: this is not a guarded closure but it only captures by value, no references are captured
let name = "Swift"
anotherControl.setAction {
    print("hello \(name)")
}

// error: closure capturing a reference must be guarded
yetAnotherControl.setAction {
   anObjectReference.method()
}

// error: strong capture not allowed without capture list
lastControl.setAction(myMethod)

// ok
lastControl.setAction { [strong self] in self.myMethod() }

#### Externally captured closures

The previous examples all show inline closures and method references. We also need to consider functions that might be received as an argument or stored in an instance, static, or global property.

It is obvious that a function should be allowed to forward a function when it is received as an argument annotated with `@guarded`. In all other cases directly forwarding a closure is not allowed. It can however be wrapped in a guarded closure pretty concisely:

func foo(_ f: @guarded () -> Void) {
   // ok
   otherFuncTakingGuardedClosure(f)
}

func foo(_ f: () -> Void) {
   // error: `f` is not guarded
   foo(f)

   // ok: explicitly wrapped 
   foo ?{ [strong f] in f() }
}

## Detailed design

The primary detail that isn't discussed above is that the implementation should take advantage of the semantics of guarded closures where possible. For example, as soon as it identifies that any of the references captured with guarded semantics has been released the entire context should be released and only a no-op stub should remain as long as the closure is referenced.

One additional detail: it should be possible to pass a guarded closure to an API that does not require a guarded closure unless there is a strong implementation reason to prohibit this.

Apple API annotation

## Source compatibility

This is a purely additive change to the language.

However, if it is introduced it is likely that a number of libraries would adopt `@guarded` semantics. This would be a moderately complex breaking change for users of the library that would impact a lot of user code. Automated migration would theoretically be possible but extremely non-trivial and therefore likely not provided.

I haven't done a careful review, but there may be some APIs that are part of Swift.org which for which it might make sense to adopt guarded callback semantics.

## Effect on ABI stability

I am not an ABI expert so takes these comments with a big grain of salt (expert input is welcome!).

This is a purely additive change. However, it is possible that guarded closures might have different ABI in order to take advantage of early release of the context.

It is also possible that an Objective-C annotation could be provided that would allow Apple frameworks to adopt guarded callback semantics when used from Swift. I am not sure what impact this would have if it happened.

## Effect on API resilience

Migrating an API from `@escaping` to `@guarded` is a breaking change.

## Future directions

It would be very useful to expose an `isAlive` property on guarded closures that would allow libraries to detect whether the guarded references are all still alive or not. This would allow libraries to discard guarded closures which have become no-ops.

It might be interesting to allow users to specify a default return value that is used after one of the guarded references has been released. If this were possible we would not have to restrict guarded closures to `Void` and `Optional` return types.

## Alternatives considered

We could leave the situation as-is. This would be very unfortunate. Some APIs are better with weak or guarded capture semantics. It should be easier to design and use these APIs in Swift.

I considered various ways of exposing API on function objects. One possibility is to allow libraries to access the whole closure context or parts of it directly along with the unbound function. Another is to allow a library to derive a new closure with a modified self capture that has guarded semantics.

These solutions are more complex than necessary and unnecessarily expose implementation details.

···

Sent from my iPad


Pitch: Weak method storage modifiers (aka weak references)
(Brent Royal-Gordon) #2

A guarded closure may be created by prefixing a bound instance method reference with the `?` sigil:

let guarded = ?myObject.myMethod

// desugars to:
let guarded = { [weak myObject] in
 guard let myObejct = myObject else {
   return
 }
 myObject.myMethod()
}

I like this in principle, but I don't like the syntax. The `?` is cryptic and looks pretty strange in prefix position. I think what I'd like to see is:

  let guarded = weak myObject.myMethod

Or:

  let guarded = weak(myObject).myMethod

This second alternative could give us the opportunity to specify a default return value if we'd like:

  let guarded = weak(myObject, else: nil).myMethod

A guarded closure may be created by prefixing an inline closure with the `?` sigil:

let guarded = ?{ flag in
 flag ? someMethod() : someOtherMethod()
}

// desugars to:
let guarded = { [weak self] flag in
 guard let strongSelf = self else {
   return
 }

 flag ? strongSelf.someMethod() : strongSelf.someOtherMethod()

This runs into the same problem as previous suggestions for addressing the weak-strong dance: There's no particular reason to privilege this behavior over any other.

I think I'd prefer to use a slightly wordier two-part solution here:

  1. If the capture list is just `[weak]` (or `[unowned]`), that's the default for all variables.

  2. A `guard` with no condition—in other words, `guard else`—tries to strengthen all weakly captured variables and enters the `else` block if any fail. (Alternative: `guard let else`.)

That makes your example into:

  let guarded = { [weak] flag in
    guard else { return }
    flag ? self.someMethod() : self.someOtherMethod()
  }

This is many more characters, but I think it's also much clearer about what's going on.

#### Self reference in escaping guarded closures

Guarded closures do not extend the lifetime of any objects unless a `strong` capture is specified in the capture list. Because this is the case users are not required to explicitly reference self using the `self.` prefix inside a guarded closure.

I think this is a bad idea, because the implicit `self` problem still exists here—it just has a slightly different shape. Here, if you accidentally use `self` without realizing it, your closure mysteriously doesn't get executed. This misbehavior is at least easier to notice, but it's still going to be hard to track down, since it's caused by something invisible in the code.

### `@guarded`

This proposal introduces the ability for arguments of function type to be annotated with the `@guarded` attribute. When `@guarded` is used the caller must supply a guarded or non-capturing closure as an argument. This ensures that users of the API think very carefully before providing an argument that captures a strong reference, and requires them to explictly state their intent when they wish to do so.

Even in this modified form, I think this is a bad idea. The person writing the closure is the one who knows whether they want it to extend objects' lifetimes. The library author accepting the closure has no idea whether the objects the closure captures are models it needs or controllers it shouldn't keep—or, for that matter, models it shouldn't keep or controllers it needs.

If we really think that this is a serious problem, we should change `@escaping` closures to always capture weak unless they're explicitly told to capture strong and be done with it. If that sounds like a hare-brained idea, then we shouldn't do it half the time and not do it the other half.

···

On Feb 19, 2017, at 2:57 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies


(Matthew Johnson) #3

A guarded closure may be created by prefixing a bound instance method reference with the `?` sigil:

let guarded = ?myObject.myMethod

// desugars to:
let guarded = { [weak myObject] in
guard let myObejct = myObject else {
  return
}
myObject.myMethod()
}

I like this in principle, but I don't like the syntax. The `?` is cryptic and looks pretty strange in prefix position. I think what I'd like to see is:

  let guarded = weak myObject.myMethod

Or:

  let guarded = weak(myObject).myMethod

This second alternative could give us the opportunity to specify a default return value if we'd like:

  let guarded = weak(myObject, else: nil).myMethod

I am certainly not wedded to the syntax I proposed. Using your syntax you could conceptualize `weak` as a function that stores a weak reference and forwards all members. That’s an interesting approach and could be useful in other places. That said, it doesn’t work with inline closures.

A guarded closure may be created by prefixing an inline closure with the `?` sigil:

let guarded = ?{ flag in
flag ? someMethod() : someOtherMethod()
}

// desugars to:
let guarded = { [weak self] flag in
guard let strongSelf = self else {
  return
}

flag ? strongSelf.someMethod() : strongSelf.someOtherMethod()

This runs into the same problem as previous suggestions for addressing the weak-strong dance: There's no particular reason to privilege this behavior over any other.

The reason is that it is a pervasive idiom when dealing with callback closures (which are extremely common). There’s no “reason" to privilege single expression closures either but we do.

I think I'd prefer to use a slightly wordier two-part solution here:

  1. If the capture list is just `[weak]` (or `[unowned]`), that's the default for all variables.

  2. A `guard` with no condition—in other words, `guard else`—tries to strengthen all weakly captured variables and enters the `else` block if any fail. (Alternative: `guard let else`.)

That makes your example into:

  let guarded = { [weak] flag in
    guard else { return }
    flag ? self.someMethod() : self.someOtherMethod()
  }

This is many more characters, but I think it's also much clearer about what's going on.

What we lose by writing this out manually is the ability for an API to express a change in default capture and require callers to acknowledge that change in default by using a sigil (or some other syntactic differentiator). Unless you suggest that the `[weak]` capture list be that indicator. If that’s the case I think it would be acceptable. Additional syntactic sugar could always be added later and the real problem I am aiming to solve is allowing APIs to require a change in default capture.

#### Self reference in escaping guarded closures

Guarded closures do not extend the lifetime of any objects unless a `strong` capture is specified in the capture list. Because this is the case users are not required to explicitly reference self using the `self.` prefix inside a guarded closure.

I think this is a bad idea, because the implicit `self` problem still exists here—it just has a slightly different shape. Here, if you accidentally use `self` without realizing it, your closure mysteriously doesn't get executed. This misbehavior is at least easier to notice, but it's still going to be hard to track down, since it's caused by something invisible in the code.

Lets consider the possible reasons somebody might make the mistake you describe above:

1. The most likely scenario where requiring `self.` would help catch a mistake is when a user intended to bind a local name and capture that instead. Requiring `self` would indeed help prevent this accident and also help readers to spot it. They did not intend to capture `self` at all. Requiring them to type `self.` makes it clear that they did.

2. They intended to capture a strong reference to self and forgot to specify the capture list entry. Would requiring them to type `self.` help remind them to add the capture list entry? Probably not - they intended to add it and just forgot. but I suppose it’s possible.

3. They don’t really understand how Swift reference counting and guarded closures work. If this is the case they are in trouble until they get up that learning curve. How much does requiring `self.` in guarded closures help with that?

4. The only other possible accident is that the user referenced some aspect of `self` despite not actually needing to use it. This can happen but I doubt it’s worth placing too much emphasis on.

The question in my mind is how do we get the most value out of requiring the `self.` prefix in closures. Do we get the most benefit if it is required exclusively where it may cause a reference cycle? Or do we get the most benefit if it is required any time users should pay close attention to what they are doing with `self`. If we reduce the need to use it that will call additional attention to places where it is required.

How frequently will requiring `self.` help catch these kinds of errors relative to the increased reference cycles it might help draw attention to if it were not required so pervasively? I could be convinced either way. I don’t think the answer is obvious.

### `@guarded`

This proposal introduces the ability for arguments of function type to be annotated with the `@guarded` attribute. When `@guarded` is used the caller must supply a guarded or non-capturing closure as an argument. This ensures that users of the API think very carefully before providing an argument that captures a strong reference, and requires them to explictly state their intent when they wish to do so.

Even in this modified form, I think this is a bad idea. The person writing the closure is the one who knows whether they want it to extend objects' lifetimes. The library author accepting the closure has no idea whether the objects the closure captures are models it needs or controllers it shouldn't keep—or, for that matter, models it shouldn't keep or controllers it needs.

If we really think that this is a serious problem, we should change `@escaping` closures to always capture weak unless they're explicitly told to capture strong and be done with it. If that sounds like a hare-brained idea, then we shouldn't do it half the time and not do it the other half.

What I am proposing to do is pretty much exactly that, except it uses a mechanism that makes that change in default visible at the call site be requiring a sigil. This means that callers must be aware of the change in default - if they aren’t then they will get a compiler error.

I believe there is an important class of API for which strong capture is the wrong default. That is why I am proposing a mechanism to allow an API to declare this change in default and require callers to acknowledge the change in default.

···

On Feb 19, 2017, at 11:35 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

On Feb 19, 2017, at 2:57 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

--
Brent Royal-Gordon
Architechies


(Florent Bruneau) #4

I like the idea of weak closures even if this may conflict with the fact closures are already internally refcounted object. While we cannot have weak references to closure in the language, using `weak` might be misleading in some ways.

In term of syntax, the `weak` keyword looks nice (much better than the '?' and @guarded once). The weak could be used on both sides.

addAction(action: weak Action)
addAction(action: weak myAction)

However, I think this might still look weird with inlined closures, in particular in case you use the suffix syntax for closures:

addAction() weak {
    /* */
}
···

Le 20 févr. 2017 à 06:35, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

On Feb 19, 2017, at 2:57 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

A guarded closure may be created by prefixing a bound instance method reference with the `?` sigil:

let guarded = ?myObject.myMethod

// desugars to:
let guarded = { [weak myObject] in
guard let myObejct = myObject else {
  return
}
myObject.myMethod()
}

I like this in principle, but I don't like the syntax. The `?` is cryptic and looks pretty strange in prefix position. I think what I'd like to see is:

  let guarded = weak myObject.myMethod


(Matthew Johnson) #5

We wouldn’t want to use `weak` if this has the guarded behavior I proposed. We could use `guarded` though.

There are a couple of important things enabled by having the language implement the guarded behavior.

First, the implementation could release the entire closure context as soon as one of the guarded captures is released. This is not possible if users write the code out manually. Second, in the future directions I discuss allowing libraries to detect that the guard has been triggered and the closure has become a no-op. This is also not possible when users write the guard out manually and would be quite desirable for libraries that want to discard their reference to the closure after it becomes a no-op.

···

On Feb 20, 2017, at 8:33 AM, Florent Bruneau <florent.bruneau@intersec.com> wrote:

Le 20 févr. 2017 à 06:35, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

On Feb 19, 2017, at 2:57 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

A guarded closure may be created by prefixing a bound instance method reference with the `?` sigil:

let guarded = ?myObject.myMethod

// desugars to:
let guarded = { [weak myObject] in
guard let myObejct = myObject else {
 return
}
myObject.myMethod()
}

I like this in principle, but I don't like the syntax. The `?` is cryptic and looks pretty strange in prefix position. I think what I'd like to see is:

  let guarded = weak myObject.myMethod

I like the idea of weak closures even if this may conflict with the fact closures are already internally refcounted object. While we cannot have weak references to closure in the language, using `weak` might be misleading in some ways.

In term of syntax, the `weak` keyword looks nice (much better than the '?' and @guarded once). The weak could be used on both sides.

addAction(action: weak Action)
addAction(action: weak myAction)

However, I think this might still look weird with inlined closures, in particular in case you use the suffix syntax for closures:

addAction() weak {
   /* */
}

(David Hart) #6

Hi Matthew,

Finally found time to read the proposal. Much better than the previous iterration. I’m not a huge fan of the proposed syntax either but don’t have the energy for bike shedding :slight_smile:

As a side note, I’d like to say that I would not use this very often. I much more often use unowned capture variables when its clear that the closure can’t outlive the captured variable. For example, my code often looks like this:

class MyViewController : UIViewController {
    var button: ActionButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        button.addAction { [unowned self] in
            print("self: \(self)")
        }
    }
}

···

On 20 Feb 2017, at 18:01, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

On Feb 20, 2017, at 8:33 AM, Florent Bruneau <florent.bruneau@intersec.com> wrote:

Le 20 févr. 2017 à 06:35, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> a écrit :

On Feb 19, 2017, at 2:57 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org> wrote:

A guarded closure may be created by prefixing a bound instance method reference with the `?` sigil:

let guarded = ?myObject.myMethod

// desugars to:
let guarded = { [weak myObject] in
guard let myObejct = myObject else {
return
}
myObject.myMethod()
}

I like this in principle, but I don't like the syntax. The `?` is cryptic and looks pretty strange in prefix position. I think what I'd like to see is:

  let guarded = weak myObject.myMethod

I like the idea of weak closures even if this may conflict with the fact closures are already internally refcounted object. While we cannot have weak references to closure in the language, using `weak` might be misleading in some ways.

In term of syntax, the `weak` keyword looks nice (much better than the '?' and @guarded once). The weak could be used on both sides.

addAction(action: weak Action)
addAction(action: weak myAction)

However, I think this might still look weird with inlined closures, in particular in case you use the suffix syntax for closures:

addAction() weak {
  /* */
}

We wouldn’t want to use `weak` if this has the guarded behavior I proposed. We could use `guarded` though.

There are a couple of important things enabled by having the language implement the guarded behavior.

First, the implementation could release the entire closure context as soon as one of the guarded captures is released. This is not possible if users write the code out manually. Second, in the future directions I discuss allowing libraries to detect that the guard has been triggered and the closure has become a no-op. This is also not possible when users write the guard out manually and would be quite desirable for libraries that want to discard their reference to the closure after it becomes a no-op.

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


(Matthew Johnson) #7

Hi Matthew,

Finally found time to read the proposal. Much better than the previous iterration. I’m not a huge fan of the proposed syntax either but don’t have the energy for bike shedding :slight_smile:

The requirements I have for the syntax are:
* it must work with both bound instance method references and closures
* it must be reasonably concise
* it must have some relationship to `@guarded` arguments

The last requirement is the reason I opted for a prefix sigil. The closest parallel we have is `inout` and that uses a prefix sigil. As soon as you decide to use a prefix sigil `?` is the obvious choice for this behavior.

I am certainly open to hearing alternatives, but they should address each of the three requirements listed above. (I mention this mostly in case anyone else reading does have energy to bikeshed on this).

As a side note, I’d like to say that I would not use this very often. I much more often use unowned capture variables when its clear that the closure can’t outlive the captured variable. For example, my code often looks like this:

class MyViewController : UIViewController {
    var button: ActionButton!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        button.addAction { [unowned self] in
            print("self: \(self)")
        }
    }
}

Hi David. Thanks for replying! I’m glad you like this approach better. I think I have finally hit on an elegant mechanism for providing safer default semantics for callbacks arguments along with a much more concise syntax for a very widespread pattern. I’ve been thinking about this problem ever since Swift first came out and am pretty pleased with this approach.

That’s certainly a reasonable way for somebody who really understands what they’re doing to write code. There is an overhead in the weak references and the guards that isn’t necessary in this case. The good news is that this would still be valid code within a guarded closure because the new proposal allows you to override the guarded behavior using the capture list.

···

On Feb 22, 2017, at 3:14 PM, David Hart <david@hartbit.com> wrote:

On 20 Feb 2017, at 18:01, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

On Feb 20, 2017, at 8:33 AM, Florent Bruneau <florent.bruneau@intersec.com <mailto:florent.bruneau@intersec.com>> wrote:

Le 20 févr. 2017 à 06:35, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

On Feb 19, 2017, at 2:57 PM, Matthew Johnson via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

A guarded closure may be created by prefixing a bound instance method reference with the `?` sigil:

let guarded = ?myObject.myMethod

// desugars to:
let guarded = { [weak myObject] in
guard let myObejct = myObject else {
return
}
myObject.myMethod()
}

I like this in principle, but I don't like the syntax. The `?` is cryptic and looks pretty strange in prefix position. I think what I'd like to see is:

  let guarded = weak myObject.myMethod

I like the idea of weak closures even if this may conflict with the fact closures are already internally refcounted object. While we cannot have weak references to closure in the language, using `weak` might be misleading in some ways.

In term of syntax, the `weak` keyword looks nice (much better than the '?' and @guarded once). The weak could be used on both sides.

addAction(action: weak Action)
addAction(action: weak myAction)

However, I think this might still look weird with inlined closures, in particular in case you use the suffix syntax for closures:

addAction() weak {
  /* */
}

We wouldn’t want to use `weak` if this has the guarded behavior I proposed. We could use `guarded` though.

There are a couple of important things enabled by having the language implement the guarded behavior.

First, the implementation could release the entire closure context as soon as one of the guarded captures is released. This is not possible if users write the code out manually. Second, in the future directions I discuss allowing libraries to detect that the guard has been triggered and the closure has become a no-op. This is also not possible when users write the guard out manually and would be quite desirable for libraries that want to discard their reference to the closure after it becomes a no-op.

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