[Proposal Update 1] A simplified notation for avoiding the weak/strong dance with closure capture lists


(E. Maloney) #1

Hello,

I sent out an earlier draft of this proposal last week, and have updated it based on feedback from the list.

The newer proposal now has a much smaller conceptual footprint to address concerns that the previous iteration was too complex and would lead to verbose notation within the capture lists.

You can find the updated gist here <https://gist.github.com/emaloney/d34ac9b134ece7c60440>, as well as pasted below.

Evan

···

---

Simplified notation for avoiding the [weak self]/strongSelf dance with closures

Proposal: TBD
Author: Evan Maloney <https://github.com/emaloney>
Status: Draft
Review manager: TBD
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#introduction>Introduction

Frequently, closures are used as completion callbacks for asynchronous operations, such as when dealing with network requests. It is quite common to model these sorts of operations in such a way that an object instance represents a request/response transaction, for example:

protocol NetworkTransaction: class
{
    enum Result {
        case Succeeded(NSData)
        case Failed(ErrorType)
    }

    func execute(completion: (Result) -> Void)
}
Here, the NetworkTransaction protocol declares the interface by which an asynchronous transaction occurs. The user of a NetworkTransaction calls the execute() function, passing in a completion function that is called at some time in the future, when the transaction completes.

For example, imagine a hypothetical DataConsumer class that uses a transaction to try to fetch some network data and process it:

class DataConsumer
{
    let transaction: NetworkTransaction

    init(transaction: NetworkTransaction)
    {
        self.transaction = transaction
    }

    func fetchData()
    {
        transaction.execute() { [weak self] result in
            guard let strongSelf = self else {
                return
            }

            switch result {
            case .Succeeded(let data):
                strongSelf.processData(data)

            case .Failed(let err):
                strongSelf.handleError(err)
            }
        }
    }

    func processData(data: NSData)
    {
        // process the data
    }

    func handleError(error: ErrorType)
    {
        // handle the error
    }
}
You'll notice the [weak self]/strongSelf dance in the fetchData() function. This is a common pattern with asynchronously-executed closures, and it signals the possibility that a closure might outlive its usefulness.

Because the NetworkTransaction may complete at any time, it is possible that the closure will execute after the DataConsumer that initiated the transaction has been deallocated. Perhaps the user has navigated elsewhere in the application and whatever data was to be fetched by DataConsumer is no longer needed.

In this case, after a DataConsumer instance goes away, we don't really want the closure doing anything. So, we capture self weakly to ensure that the closure doesn't hold a reference to the owning DataConsumer. That prevents a reference cycle and ensures that DataConsumer can be deallocated when no longer in use.

When it comes time to execute the closure, the guard statement effectively asks the question, "Is self still alive?" If the answer is no, the guard forces a return and the rest of the closure does not execute.

If self is still alive, then the weakly-captured self will be non-nil and it will be converted into a strong reference held by strongSelf for the duration of the closure's execution.

When the closure is done executing, strongSelf goes away, once again making the DataConsumer eligible for deallocation when no other references are held.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#the-problem>The Problem

The [weak self]/strongSelf dance requires common boilerplate wherever it is used, and the fact that a self-like variable with an arbitrary name adds noise within the closure. The more strongSelf is needed within the closure, the more noise there is.

Further, using a consistent name like strongSelf is by convention only; it can't be enforced by the compiler, so searching your codebase for a given keyword won't be exhaustive if team members use the wrong name.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#proposed-solution>Proposed Solution

The proposed solution adds a new capture type by repurposing the guard keyword for another use, which would look like:

transaction.execute() { [guard self] result in
    switch result {
    case .Succeeded(let data):
        self.processData(data)

    case .Failed(let err):
        self.handleError(err)
    }
}
Here, the [guard self] capture list serves as a signal that the compiler should handle the weak/strong dance itself. When encountering [guard self], the compiler should emit code that does the following:

Captures self in a weak reference on behalf of the closure
Whenever the closure is about to be executed, the weak reference is checked to see if self is still alive
If self is not alive, the closure becomes a no-op <https://en.wikipedia.org/wiki/NOP>; calling the closure returns immediately without anything inside the braces being executed
If self is alive, it is upgraded to a strong reference for the lifetime of the closure's execution. Within the closure, self is non-optional, unlike how it would be with a [weak self] capture. When the closure is done executing, the strong reference will be cleared and only the weak reference will be held on behalf of the closure.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#non-self-references>Non-self References

Because guard is an additional capture type, like weak and unowned, it can also be used to capture references other than self:

let capturingTwo = { [guard self, button] in
    // weakly capture self and button
    // but execute the closure with strong references
    // if and only if self AND button still exist
    // when the closure is being asked to execute
}
When encountering multiple references being captured via guard, the closure will execute only when all references are still alive when the closure is being asked to execute.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#limitations>Limitations

Because guard is a special capture type that causes the closure to become a no-op once a referenced object deallocates, it is only designed to be used with closures returning Void.

This limitation was deemed acceptable because it would cover the vast majority of cases, and those that it didn't cover can still fall back on the existing technique.

The compiler should emit an error if this notation is used in conjunction with a closure that has a non-Void return type.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#caveats>Caveats

This notation is not intended to be a full-fledged replacement for guard statements within the closure. We are only using guard here as a way to declare a specific memory-management behavior for references. Therefore, guard within [square brackets ] should be seen as a capture type on par with weak or unowned.

Unlike with a typical guard statement, we are not attempting to support an else or where clause, or any boolean expressions within this notation.

Rather, we're simply adding a new capture behavior and providing a means to specify an early exit if the behavior couldn't be fulfilled because one or more objects was deallocated.

The word guard was chosen as the capture type because (1) it functions as a guard, ensuring that the closure doesn't execute unless the specified objects are still alive, and (2) it obviates the need for the full-fledged guard statement that would otherwise be required to achieve the same result.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#impact-on-existing-code>Impact on Existing Code

None, since this does not affect any existing constructs. Implementation of this proposal will not result in any code breakage.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#alternatives-considered>Alternatives Considered

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#status-quo>Status Quo

The primary alternative is to do nothing, requiring developers to add boilerplate guard code and handle upgrading the weak-to-strong references manually.

As stated above, this leads to needless boilerplate that can easily be factored out by the compiler. Also, the use of a self-like variable with an arbitrary name makes it more difficult to exhaustively find such uses in large projects. With this proposal, searching for the text "[guard" is all that's necessary to find all instances of this memory management technique.

Finally, the need to declare and use alternate names to capture values that already have existing names adds visual clutter to code and serves to obscure the code's original intent, making it harder to reason about.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-an-optional-return-type>Closures with an Optional Return Type

One possible addition to this proposal would extend support to any closure returning an Optional of some kind.

If one of the objects in a guard capture list has been deallocated, executing the closure will always result in an immediate nil return.

This idea was excluded from this iteration of the proposal due to a concern that it relied on a "magic return value" (albeit a reasonable one) and a perception that the community favored a solution with a smaller conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-a-bool-return-type>Closures with a Bool Return Type

One possible addition to this proposal would extend support to any closure returning a Bool.

If one of the objects in a guard capture list has been deallocated, executing the closure will always result in an immediate false return.

This idea was excluded from this iteration of the proposal due to a concern that it relied on a "magic return value" (albeit a reasonable one) and a perception that the community favored a solution with a smaller conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-arbitrary-return-types>Closures with Arbitrary Return Types

An earlier iteration of this proposal included support for closures with arbitrary return values. The community consensus was that the proposal was too heavy-weight and tried to do too much, and would lead to verbosity within the capture declaration. As a result, this idea was removed from the proposal.

The ability to handle non-Void return values relied on supporting an else clause within a guard-based capture list:

let happinessLevel: () -> Int = { [guard self else -1] in
    var level = 0
    level += self.isHealthy ? 25 : 0
    level += !self.isHungry ? 25 : 0
    level += !self.isFearful ? 25 : 0
    level += self.hasLove ? 25 : 0
    return level
}
Here, the else clause provides a value to return in cases where self has gone away and the guard fails.

In this example, if you call happinessLevel() after self has been deallocated, the value -1 will always be returned.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#citations>Citations

Variations on this proposal were discussed earlier in the following swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution> threads:

Wanted: syntactic sugar for [weak self] callbacks <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/008713.html>
Allowing guard let self = self else { … } for weakly captured self in a closure. <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009023.html>
[Draft Proposal] A simplified notation for avoiding the weak/strong dance with closure capture lists <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009241.html>


(David Turnbull) #2

This could be the default behavior. A program can request "self", "weak
self", or "unowned self" if they want something else.

-david

···

On Fri, Feb 12, 2016 at 10:23 AM, Evan Maloney via swift-evolution < swift-evolution@swift.org> wrote:

Hello,

I sent out an earlier draft of this proposal last week, and have updated
it based on feedback from the list.

The newer proposal now has a much smaller conceptual footprint to address
concerns that the previous iteration was too complex and would lead to
verbose notation within the capture lists.

You can find the updated gist here
<https://gist.github.com/emaloney/d34ac9b134ece7c60440>, as well as
pasted below.

Evan

---

Simplified notation for avoiding the [weak self]/strongSelf dance with
closures

   - Proposal: TBD
   - Author: Evan Maloney <https://github.com/emaloney>
   - Status: *Draft*
   - Review manager: TBD

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#introduction>
Introduction

Frequently, closures are used as completion callbacks for asynchronous
operations, such as when dealing with network requests. It is quite common
to model these sorts of operations in such a way that an object instance
represents a request/response transaction, for example:

protocol NetworkTransaction: class
{
    enum Result {
        case Succeeded(NSData)
        case Failed(ErrorType)
    }

    func execute(completion: (Result) -> Void)
}

Here, the NetworkTransaction protocol declares the interface by which an
asynchronous transaction occurs. The user of a NetworkTransaction calls
the execute() function, passing in a completion function that is called
at some time in the future, when the transaction completes.

For example, imagine a hypothetical DataConsumer class that uses a
transaction to try to fetch some network data and process it:

class DataConsumer
{
    let transaction: NetworkTransaction

    init(transaction: NetworkTransaction)
    {
        self.transaction = transaction
    }

    func fetchData()
    {
        transaction.execute() { [weak self] result in
            guard let strongSelf = self else {
                return
            }

            switch result {
            case .Succeeded(let data):
                strongSelf.processData(data)

            case .Failed(let err):
                strongSelf.handleError(err)
            }
        }
    }

    func processData(data: NSData)
    {
        // process the data
    }

    func handleError(error: ErrorType)
    {
        // handle the error
    }
}

You'll notice the [weak self]/strongSelf dance in the fetchData() function.
This is a common pattern with asynchronously-executed closures, and it
signals the possibility that a closure might outlive its usefulness.

Because the NetworkTransaction may complete at any time, it is possible
that the closure will execute after the DataConsumer that initiated the
transaction has been deallocated. Perhaps the user has navigated elsewhere
in the application and whatever data was to be fetched by DataConsumer is
no longer needed.

In this case, after a DataConsumer instance goes away, we don't really
want the closure doing anything. So, we capture self weakly to ensure
that the closure doesn't hold a reference to the owning DataConsumer.
That prevents a reference cycle and ensures that DataConsumer can be
deallocated when no longer in use.

When it comes time to execute the closure, the guard statement
effectively asks the question, "Is self still alive?" If the answer is
no, the guard forces a return and the rest of the closure does not execute.

If self *is* still alive, then the weakly-captured self will be non-nil and
it will be converted into a strong reference held by strongSelf for the
duration of the closure's execution.

When the closure is done executing, strongSelf goes away, once again
making the DataConsumer eligible for deallocation when no other
references are held.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#the-problem>The
Problem

The [weak self]/strongSelf dance requires common boilerplate wherever it
is used, and the fact that a self-like variable with an arbitrary name
adds noise within the closure. The more strongSelf is needed within the
closure, the more noise there is.

Further, using a consistent name like strongSelf is by convention only;
it can't be enforced by the compiler, so searching your codebase for a
given keyword won't be exhaustive if team members use the wrong name.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#proposed-solution>Proposed
Solution

The proposed solution adds a new capture type by repurposing the guard keyword
for another use, which would look like:

transaction.execute() { [guard self] result in
    switch result {
    case .Succeeded(let data):
        self.processData(data)

    case .Failed(let err):
        self.handleError(err)
    }
}

Here, the [guard self] capture list serves as a signal that the compiler
should handle the weak/strong dance itself. When encountering [guard self],
the compiler should emit code that does the following:

   - Captures self in a weak reference on behalf of the closure
   - Whenever the closure is about to be executed, the weak reference is
   checked to see if self is still alive
      - If self is not alive, the closure becomes a no-op
      <https://en.wikipedia.org/wiki/NOP>; calling the closure returns
      immediately without anything inside the braces being executed
      - If self is alive, it is upgraded to a strong reference for the
      lifetime of the closure's execution. Within the closure, self is
      non-optional, unlike how it would be with a [weak self] capture.
      When the closure is done executing, the strong reference will be cleared
      and only the weak reference will be held on behalf of the closure.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#non-self-references>
Non-self References

Because guard is an additional capture type, like weak and unowned, it
can also be used to capture references other than self:

let capturingTwo = { [guard self, button] in
    // weakly capture self and button
    // but execute the closure with strong references
    // if and only if self AND button still exist
    // when the closure is being asked to execute
}

When encountering multiple references being captured via guard, the
closure will execute *only* when *all* references are still alive when
the closure is being asked to execute.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#limitations>
Limitations

Because guard is a special capture type that causes the closure to become
a no-op once a referenced object deallocates, it is only designed to be
used with closures returning Void.

This limitation was deemed acceptable because it would cover the vast
majority of cases, and those that it didn't cover can still fall back on
the existing technique.

The compiler should emit an error if this notation is used in conjunction
with a closure that has a non-Void return type.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#caveats>Caveats

This notation is not intended to be a full-fledged replacement for guard
statements within the closure. We are only using guard here as a way to
declare a specific memory-management behavior for references. Therefore,
guard within [square brackets ] should be seen as a capture type on par
with weak or unowned.

Unlike with a typical guard statement, we are not attempting to support
an else or where clause, or any boolean expressions within this notation.

Rather, we're simply adding a new capture behavior and providing a means
to specify an early exit if the behavior couldn't be fulfilled because one
or more objects was deallocated.

The word guard was chosen as the capture type because (1) it functions as
a guard, ensuring that the closure doesn't execute unless the specified
objects are still alive, and (2) it obviates the need for the full-fledged
guard statement that would otherwise be required to achieve the same
result.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#impact-on-existing-code>Impact
on Existing Code

None, since this does not affect any existing constructs. Implementation
of this proposal will not result in any code breakage.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#alternatives-considered>Alternatives
Considered
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#status-quo>Status
Quo

The primary alternative is to do nothing, requiring developers to add
boilerplate guard code and handle upgrading the weak-to-strong references
manually.

As stated above, this leads to needless boilerplate that can easily be
factored out by the compiler. Also, the use of a self-like variable with
an arbitrary name makes it more difficult to exhaustively find such uses in
large projects. With this proposal, searching for the text "[guard" is
all that's necessary to find all instances of this memory management
technique.

Finally, the need to declare and use alternate names to capture values
that already have existing names adds visual clutter to code and serves to
obscure the code's original intent, making it harder to reason about.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-an-optional-return-type>Closures
with an Optional Return Type

One possible addition to this proposal would extend support to any closure
returning an Optional of some kind.

If one of the objects in a guard capture list has been deallocated,
executing the closure will always result in an immediate nil return.

This idea was excluded from this iteration of the proposal due to a
concern that it relied on a "magic return value" (albeit a reasonable one)
and a perception that the community favored a solution with a smaller
conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-a-bool-return-type>Closures
with a Bool Return Type

One possible addition to this proposal would extend support to any closure
returning a Bool.

If one of the objects in a guard capture list has been deallocated,
executing the closure will always result in an immediate false return.

This idea was excluded from this iteration of the proposal due to a
concern that it relied on a "magic return value" (albeit a reasonable one)
and a perception that the community favored a solution with a smaller
conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-arbitrary-return-types>Closures
with Arbitrary Return Types

An earlier iteration of this proposal included support for closures with
arbitrary return values. The community consensus was that the proposal was
too heavy-weight and tried to do too much, and would lead to verbosity
within the capture declaration. As a result, this idea was removed from the
proposal.

The ability to handle non-Void return values relied on supporting an else clause
within a guard-based capture list:

let happinessLevel: () -> Int = { [guard self else -1] in
    var level = 0
    level += self.isHealthy ? 25 : 0
    level += !self.isHungry ? 25 : 0
    level += !self.isFearful ? 25 : 0
    level += self.hasLove ? 25 : 0
    return level
}

Here, the else clause provides a value to return in cases where self has
gone away and the guard fails.

In this example, if you call happinessLevel() after self has been
deallocated, the value -1 will always be returned.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#citations>Citations

Variations on this proposal were discussed earlier in the following
swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
threads:

   - Wanted: syntactic sugar for [weak self] callbacks
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/008713.html>
   - Allowing guard let self = self else { … } for weakly captured self
   in a closure.
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009023.html>
   - [Draft Proposal] A simplified notation for avoiding the weak/strong
   dance with closure capture lists
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009241.html>

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


(Jason Gregori) #3

I like this much better :+1:t3:.

Although I think adding weak in there would make what's going on much
clearer. [guard self] looks more like it returns if self is nil. [guard
weak self] obviously has something to do with weak semantics.

···

On Fri, Feb 12, 2016 at 10:44 AM, Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

So long as it's technically possible, this would rock.

-- E

On Feb 12, 2016, at 11:23 AM, Evan Maloney <emaloney@gilt.com> wrote:

Hello,

I sent out an earlier draft of this proposal last week, and have updated
it based on feedback from the list.

The newer proposal now has a much smaller conceptual footprint to address
concerns that the previous iteration was too complex and would lead to
verbose notation within the capture lists.

You can find the updated gist here
<https://gist.github.com/emaloney/d34ac9b134ece7c60440>, as well as
pasted below.

Evan

---

Simplified notation for avoiding the [weak self]/strongSelf dance with
closures

   - Proposal: TBD
   - Author: Evan Maloney <https://github.com/emaloney>
   - Status: *Draft*
   - Review manager: TBD

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#introduction>
Introduction

Frequently, closures are used as completion callbacks for asynchronous
operations, such as when dealing with network requests. It is quite common
to model these sorts of operations in such a way that an object instance
represents a request/response transaction, for example:

protocol NetworkTransaction: class
{
    enum Result {
        case Succeeded(NSData)
        case Failed(ErrorType)
    }

    func execute(completion: (Result) -> Void)
}

Here, the NetworkTransaction protocol declares the interface by which an
asynchronous transaction occurs. The user of a NetworkTransaction calls
the execute() function, passing in a completion function that is called
at some time in the future, when the transaction completes.

For example, imagine a hypothetical DataConsumer class that uses a
transaction to try to fetch some network data and process it:

class DataConsumer
{
    let transaction: NetworkTransaction

    init(transaction: NetworkTransaction)
    {
        self.transaction = transaction
    }

    func fetchData()
    {
        transaction.execute() { [weak self] result in
            guard let strongSelf = self else {
                return
            }

            switch result {
            case .Succeeded(let data):
                strongSelf.processData(data)

            case .Failed(let err):
                strongSelf.handleError(err)
            }
        }
    }

    func processData(data: NSData)
    {
        // process the data
    }

    func handleError(error: ErrorType)
    {
        // handle the error
    }
}

You'll notice the [weak self]/strongSelf dance in the fetchData() function.
This is a common pattern with asynchronously-executed closures, and it
signals the possibility that a closure might outlive its usefulness.

Because the NetworkTransaction may complete at any time, it is possible
that the closure will execute after the DataConsumer that initiated the
transaction has been deallocated. Perhaps the user has navigated elsewhere
in the application and whatever data was to be fetched by DataConsumer is
no longer needed.

In this case, after a DataConsumer instance goes away, we don't really
want the closure doing anything. So, we capture self weakly to ensure
that the closure doesn't hold a reference to the owning DataConsumer.
That prevents a reference cycle and ensures that DataConsumer can be
deallocated when no longer in use.

When it comes time to execute the closure, the guard statement
effectively asks the question, "Is self still alive?" If the answer is
no, the guard forces a return and the rest of the closure does not execute.

If self *is* still alive, then the weakly-captured self will be non-nil and
it will be converted into a strong reference held by strongSelf for the
duration of the closure's execution.

When the closure is done executing, strongSelf goes away, once again
making the DataConsumer eligible for deallocation when no other
references are held.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#the-problem>The
Problem

The [weak self]/strongSelf dance requires common boilerplate wherever it
is used, and the fact that a self-like variable with an arbitrary name
adds noise within the closure. The more strongSelf is needed within the
closure, the more noise there is.

Further, using a consistent name like strongSelf is by convention only;
it can't be enforced by the compiler, so searching your codebase for a
given keyword won't be exhaustive if team members use the wrong name.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#proposed-solution>Proposed
Solution

The proposed solution adds a new capture type by repurposing the guard keyword
for another use, which would look like:

transaction.execute() { [guard self] result in
    switch result {
    case .Succeeded(let data):
        self.processData(data)

    case .Failed(let err):
        self.handleError(err)
    }
}

Here, the [guard self] capture list serves as a signal that the compiler
should handle the weak/strong dance itself. When encountering [guard self],
the compiler should emit code that does the following:

   - Captures self in a weak reference on behalf of the closure
   - Whenever the closure is about to be executed, the weak reference is
   checked to see if self is still alive
      - If self is not alive, the closure becomes a no-op
      <https://en.wikipedia.org/wiki/NOP>; calling the closure returns
      immediately without anything inside the braces being executed
      - If self is alive, it is upgraded to a strong reference for the
      lifetime of the closure's execution. Within the closure, self is
      non-optional, unlike how it would be with a [weak self] capture.
      When the closure is done executing, the strong reference will be cleared
      and only the weak reference will be held on behalf of the closure.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#non-self-references>
Non-self References

Because guard is an additional capture type, like weak and unowned, it
can also be used to capture references other than self:

let capturingTwo = { [guard self, button] in
    // weakly capture self and button
    // but execute the closure with strong references
    // if and only if self AND button still exist
    // when the closure is being asked to execute
}

When encountering multiple references being captured via guard, the
closure will execute *only* when *all* references are still alive when
the closure is being asked to execute.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#limitations>
Limitations

Because guard is a special capture type that causes the closure to become
a no-op once a referenced object deallocates, it is only designed to be
used with closures returning Void.

This limitation was deemed acceptable because it would cover the vast
majority of cases, and those that it didn't cover can still fall back on
the existing technique.

The compiler should emit an error if this notation is used in conjunction
with a closure that has a non-Void return type.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#caveats>Caveats

This notation is not intended to be a full-fledged replacement for guard
statements within the closure. We are only using guard here as a way to
declare a specific memory-management behavior for references. Therefore,
guard within [square brackets ] should be seen as a capture type on par
with weak or unowned.

Unlike with a typical guard statement, we are not attempting to support
an else or where clause, or any boolean expressions within this notation.

Rather, we're simply adding a new capture behavior and providing a means
to specify an early exit if the behavior couldn't be fulfilled because one
or more objects was deallocated.

The word guard was chosen as the capture type because (1) it functions as
a guard, ensuring that the closure doesn't execute unless the specified
objects are still alive, and (2) it obviates the need for the full-fledged
guard statement that would otherwise be required to achieve the same
result.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#impact-on-existing-code>Impact
on Existing Code

None, since this does not affect any existing constructs. Implementation
of this proposal will not result in any code breakage.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#alternatives-considered>Alternatives
Considered
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#status-quo>Status
Quo

The primary alternative is to do nothing, requiring developers to add
boilerplate guard code and handle upgrading the weak-to-strong references
manually.

As stated above, this leads to needless boilerplate that can easily be
factored out by the compiler. Also, the use of a self-like variable with
an arbitrary name makes it more difficult to exhaustively find such uses in
large projects. With this proposal, searching for the text "[guard" is
all that's necessary to find all instances of this memory management
technique.

Finally, the need to declare and use alternate names to capture values
that already have existing names adds visual clutter to code and serves to
obscure the code's original intent, making it harder to reason about.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-an-optional-return-type>Closures
with an Optional Return Type

One possible addition to this proposal would extend support to any closure
returning an Optional of some kind.

If one of the objects in a guard capture list has been deallocated,
executing the closure will always result in an immediate nil return.

This idea was excluded from this iteration of the proposal due to a
concern that it relied on a "magic return value" (albeit a reasonable one)
and a perception that the community favored a solution with a smaller
conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-a-bool-return-type>Closures
with a Bool Return Type

One possible addition to this proposal would extend support to any closure
returning a Bool.

If one of the objects in a guard capture list has been deallocated,
executing the closure will always result in an immediate false return.

This idea was excluded from this iteration of the proposal due to a
concern that it relied on a "magic return value" (albeit a reasonable one)
and a perception that the community favored a solution with a smaller
conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-arbitrary-return-types>Closures
with Arbitrary Return Types

An earlier iteration of this proposal included support for closures with
arbitrary return values. The community consensus was that the proposal was
too heavy-weight and tried to do too much, and would lead to verbosity
within the capture declaration. As a result, this idea was removed from the
proposal.

The ability to handle non-Void return values relied on supporting an else clause
within a guard-based capture list:

let happinessLevel: () -> Int = { [guard self else -1] in
    var level = 0
    level += self.isHealthy ? 25 : 0
    level += !self.isHungry ? 25 : 0
    level += !self.isFearful ? 25 : 0
    level += self.hasLove ? 25 : 0
    return level
}

Here, the else clause provides a value to return in cases where self has
gone away and the guard fails.

In this example, if you call happinessLevel() after self has been
deallocated, the value -1 will always be returned.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#citations>Citations

Variations on this proposal were discussed earlier in the following
swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
threads:

   - Wanted: syntactic sugar for [weak self] callbacks
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/008713.html>
   - Allowing guard let self = self else { … } for weakly captured self
   in a closure.
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009023.html>
   - [Draft Proposal] A simplified notation for avoiding the weak/strong
   dance with closure capture lists
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009241.html>

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


(Erica Sadun) #4

So long as it's technically possible, this would rock.

-- E

···

On Feb 12, 2016, at 11:23 AM, Evan Maloney <emaloney@gilt.com> wrote:

Hello,

I sent out an earlier draft of this proposal last week, and have updated it based on feedback from the list.

The newer proposal now has a much smaller conceptual footprint to address concerns that the previous iteration was too complex and would lead to verbose notation within the capture lists.

You can find the updated gist here <https://gist.github.com/emaloney/d34ac9b134ece7c60440>, as well as pasted below.

Evan

---

Simplified notation for avoiding the [weak self]/strongSelf dance with closures

Proposal: TBD
Author: Evan Maloney <https://github.com/emaloney>
Status: Draft
Review manager: TBD
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#introduction>Introduction

Frequently, closures are used as completion callbacks for asynchronous operations, such as when dealing with network requests. It is quite common to model these sorts of operations in such a way that an object instance represents a request/response transaction, for example:

protocol NetworkTransaction: class
{
    enum Result {
        case Succeeded(NSData)
        case Failed(ErrorType)
    }

    func execute(completion: (Result) -> Void)
}
Here, the NetworkTransaction protocol declares the interface by which an asynchronous transaction occurs. The user of a NetworkTransaction calls the execute() function, passing in a completion function that is called at some time in the future, when the transaction completes.

For example, imagine a hypothetical DataConsumer class that uses a transaction to try to fetch some network data and process it:

class DataConsumer
{
    let transaction: NetworkTransaction

    init(transaction: NetworkTransaction)
    {
        self.transaction = transaction
    }

    func fetchData()
    {
        transaction.execute() { [weak self] result in
            guard let strongSelf = self else {
                return
            }

            switch result {
            case .Succeeded(let data):
                strongSelf.processData(data)

            case .Failed(let err):
                strongSelf.handleError(err)
            }
        }
    }

    func processData(data: NSData)
    {
        // process the data
    }

    func handleError(error: ErrorType)
    {
        // handle the error
    }
}
You'll notice the [weak self]/strongSelf dance in the fetchData() function. This is a common pattern with asynchronously-executed closures, and it signals the possibility that a closure might outlive its usefulness.

Because the NetworkTransaction may complete at any time, it is possible that the closure will execute after the DataConsumer that initiated the transaction has been deallocated. Perhaps the user has navigated elsewhere in the application and whatever data was to be fetched by DataConsumer is no longer needed.

In this case, after a DataConsumer instance goes away, we don't really want the closure doing anything. So, we capture self weakly to ensure that the closure doesn't hold a reference to the owning DataConsumer. That prevents a reference cycle and ensures that DataConsumer can be deallocated when no longer in use.

When it comes time to execute the closure, the guard statement effectively asks the question, "Is self still alive?" If the answer is no, the guard forces a return and the rest of the closure does not execute.

If self is still alive, then the weakly-captured self will be non-nil and it will be converted into a strong reference held by strongSelf for the duration of the closure's execution.

When the closure is done executing, strongSelf goes away, once again making the DataConsumer eligible for deallocation when no other references are held.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#the-problem>The Problem

The [weak self]/strongSelf dance requires common boilerplate wherever it is used, and the fact that a self-like variable with an arbitrary name adds noise within the closure. The more strongSelf is needed within the closure, the more noise there is.

Further, using a consistent name like strongSelf is by convention only; it can't be enforced by the compiler, so searching your codebase for a given keyword won't be exhaustive if team members use the wrong name.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#proposed-solution>Proposed Solution

The proposed solution adds a new capture type by repurposing the guard keyword for another use, which would look like:

transaction.execute() { [guard self] result in
    switch result {
    case .Succeeded(let data):
        self.processData(data)

    case .Failed(let err):
        self.handleError(err)
    }
}
Here, the [guard self] capture list serves as a signal that the compiler should handle the weak/strong dance itself. When encountering [guard self], the compiler should emit code that does the following:

Captures self in a weak reference on behalf of the closure
Whenever the closure is about to be executed, the weak reference is checked to see if self is still alive
If self is not alive, the closure becomes a no-op <https://en.wikipedia.org/wiki/NOP>; calling the closure returns immediately without anything inside the braces being executed
If self is alive, it is upgraded to a strong reference for the lifetime of the closure's execution. Within the closure, self is non-optional, unlike how it would be with a [weak self] capture. When the closure is done executing, the strong reference will be cleared and only the weak reference will be held on behalf of the closure.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#non-self-references>Non-self References

Because guard is an additional capture type, like weak and unowned, it can also be used to capture references other than self:

let capturingTwo = { [guard self, button] in
    // weakly capture self and button
    // but execute the closure with strong references
    // if and only if self AND button still exist
    // when the closure is being asked to execute
}
When encountering multiple references being captured via guard, the closure will execute only when all references are still alive when the closure is being asked to execute.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#limitations>Limitations

Because guard is a special capture type that causes the closure to become a no-op once a referenced object deallocates, it is only designed to be used with closures returning Void.

This limitation was deemed acceptable because it would cover the vast majority of cases, and those that it didn't cover can still fall back on the existing technique.

The compiler should emit an error if this notation is used in conjunction with a closure that has a non-Void return type.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#caveats>Caveats

This notation is not intended to be a full-fledged replacement for guard statements within the closure. We are only using guard here as a way to declare a specific memory-management behavior for references. Therefore, guard within [square brackets ] should be seen as a capture type on par with weak or unowned.

Unlike with a typical guard statement, we are not attempting to support an else or where clause, or any boolean expressions within this notation.

Rather, we're simply adding a new capture behavior and providing a means to specify an early exit if the behavior couldn't be fulfilled because one or more objects was deallocated.

The word guard was chosen as the capture type because (1) it functions as a guard, ensuring that the closure doesn't execute unless the specified objects are still alive, and (2) it obviates the need for the full-fledged guard statement that would otherwise be required to achieve the same result.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#impact-on-existing-code>Impact on Existing Code

None, since this does not affect any existing constructs. Implementation of this proposal will not result in any code breakage.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#alternatives-considered>Alternatives Considered

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#status-quo>Status Quo

The primary alternative is to do nothing, requiring developers to add boilerplate guard code and handle upgrading the weak-to-strong references manually.

As stated above, this leads to needless boilerplate that can easily be factored out by the compiler. Also, the use of a self-like variable with an arbitrary name makes it more difficult to exhaustively find such uses in large projects. With this proposal, searching for the text "[guard" is all that's necessary to find all instances of this memory management technique.

Finally, the need to declare and use alternate names to capture values that already have existing names adds visual clutter to code and serves to obscure the code's original intent, making it harder to reason about.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-an-optional-return-type>Closures with an Optional Return Type

One possible addition to this proposal would extend support to any closure returning an Optional of some kind.

If one of the objects in a guard capture list has been deallocated, executing the closure will always result in an immediate nil return.

This idea was excluded from this iteration of the proposal due to a concern that it relied on a "magic return value" (albeit a reasonable one) and a perception that the community favored a solution with a smaller conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-a-bool-return-type>Closures with a Bool Return Type

One possible addition to this proposal would extend support to any closure returning a Bool.

If one of the objects in a guard capture list has been deallocated, executing the closure will always result in an immediate false return.

This idea was excluded from this iteration of the proposal due to a concern that it relied on a "magic return value" (albeit a reasonable one) and a perception that the community favored a solution with a smaller conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-arbitrary-return-types>Closures with Arbitrary Return Types

An earlier iteration of this proposal included support for closures with arbitrary return values. The community consensus was that the proposal was too heavy-weight and tried to do too much, and would lead to verbosity within the capture declaration. As a result, this idea was removed from the proposal.

The ability to handle non-Void return values relied on supporting an else clause within a guard-based capture list:

let happinessLevel: () -> Int = { [guard self else -1] in
    var level = 0
    level += self.isHealthy ? 25 : 0
    level += !self.isHungry ? 25 : 0
    level += !self.isFearful ? 25 : 0
    level += self.hasLove ? 25 : 0
    return level
}
Here, the else clause provides a value to return in cases where self has gone away and the guard fails.

In this example, if you call happinessLevel() after self has been deallocated, the value -1 will always be returned.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#citations>Citations

Variations on this proposal were discussed earlier in the following swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution> threads:

Wanted: syntactic sugar for [weak self] callbacks <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/008713.html>
Allowing guard let self = self else { … } for weakly captured self in a closure. <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009023.html>
[Draft Proposal] A simplified notation for avoiding the weak/strong dance with closure capture lists <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009241.html>


(Brent Royal-Gordon) #5

Alternatives Considered

Status Quo

Closures with an Optional Return Type

Closures with a Bool Return Type

Closures with Arbitrary Return Types

I would also discuss the alternative of allowing `self` to be shadowed so that you could write `guard let self = self else { return }`. Your proposal still has some advantages over that one, but I would like to see those explicitly called out and discussed.

···

--
Brent Royal-Gordon
Architechies


(Vanderlei Martinelli) #6

Well... While I think this could be a good idea, I think that the current
implementation is already a “syntactic sugar” for the weak and strong
dance. A simple `if let self = self` inside the closure (not allowed today)
would be enough if we are going to change the current behaviour.

-Van

···

On Fri, Feb 12, 2016 at 4:23 PM, Evan Maloney via swift-evolution < swift-evolution@swift.org> wrote:

Hello,

I sent out an earlier draft of this proposal last week, and have updated
it based on feedback from the list.

The newer proposal now has a much smaller conceptual footprint to address
concerns that the previous iteration was too complex and would lead to
verbose notation within the capture lists.

You can find the updated gist here
<https://gist.github.com/emaloney/d34ac9b134ece7c60440>, as well as
pasted below.

Evan

---

Simplified notation for avoiding the [weak self]/strongSelf dance with
closures

   - Proposal: TBD
   - Author: Evan Maloney <https://github.com/emaloney>
   - Status: *Draft*
   - Review manager: TBD

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#introduction>
Introduction

Frequently, closures are used as completion callbacks for asynchronous
operations, such as when dealing with network requests. It is quite common
to model these sorts of operations in such a way that an object instance
represents a request/response transaction, for example:

protocol NetworkTransaction: class
{
    enum Result {
        case Succeeded(NSData)
        case Failed(ErrorType)
    }

    func execute(completion: (Result) -> Void)
}

Here, the NetworkTransaction protocol declares the interface by which an
asynchronous transaction occurs. The user of a NetworkTransaction calls
the execute() function, passing in a completion function that is called
at some time in the future, when the transaction completes.

For example, imagine a hypothetical DataConsumer class that uses a
transaction to try to fetch some network data and process it:

class DataConsumer
{
    let transaction: NetworkTransaction

    init(transaction: NetworkTransaction)
    {
        self.transaction = transaction
    }

    func fetchData()
    {
        transaction.execute() { [weak self] result in
            guard let strongSelf = self else {
                return
            }

            switch result {
            case .Succeeded(let data):
                strongSelf.processData(data)

            case .Failed(let err):
                strongSelf.handleError(err)
            }
        }
    }

    func processData(data: NSData)
    {
        // process the data
    }

    func handleError(error: ErrorType)
    {
        // handle the error
    }
}

You'll notice the [weak self]/strongSelf dance in the fetchData() function.
This is a common pattern with asynchronously-executed closures, and it
signals the possibility that a closure might outlive its usefulness.

Because the NetworkTransaction may complete at any time, it is possible
that the closure will execute after the DataConsumer that initiated the
transaction has been deallocated. Perhaps the user has navigated elsewhere
in the application and whatever data was to be fetched by DataConsumer is
no longer needed.

In this case, after a DataConsumer instance goes away, we don't really
want the closure doing anything. So, we capture self weakly to ensure
that the closure doesn't hold a reference to the owning DataConsumer.
That prevents a reference cycle and ensures that DataConsumer can be
deallocated when no longer in use.

When it comes time to execute the closure, the guard statement
effectively asks the question, "Is self still alive?" If the answer is
no, the guard forces a return and the rest of the closure does not execute.

If self *is* still alive, then the weakly-captured self will be non-nil and
it will be converted into a strong reference held by strongSelf for the
duration of the closure's execution.

When the closure is done executing, strongSelf goes away, once again
making the DataConsumer eligible for deallocation when no other
references are held.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#the-problem>The
Problem

The [weak self]/strongSelf dance requires common boilerplate wherever it
is used, and the fact that a self-like variable with an arbitrary name
adds noise within the closure. The more strongSelf is needed within the
closure, the more noise there is.

Further, using a consistent name like strongSelf is by convention only;
it can't be enforced by the compiler, so searching your codebase for a
given keyword won't be exhaustive if team members use the wrong name.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#proposed-solution>Proposed
Solution

The proposed solution adds a new capture type by repurposing the guard keyword
for another use, which would look like:

transaction.execute() { [guard self] result in
    switch result {
    case .Succeeded(let data):
        self.processData(data)

    case .Failed(let err):
        self.handleError(err)
    }
}

Here, the [guard self] capture list serves as a signal that the compiler
should handle the weak/strong dance itself. When encountering [guard self],
the compiler should emit code that does the following:

   - Captures self in a weak reference on behalf of the closure
   - Whenever the closure is about to be executed, the weak reference is
   checked to see if self is still alive
      - If self is not alive, the closure becomes a no-op
      <https://en.wikipedia.org/wiki/NOP>; calling the closure returns
      immediately without anything inside the braces being executed
      - If self is alive, it is upgraded to a strong reference for the
      lifetime of the closure's execution. Within the closure, self is
      non-optional, unlike how it would be with a [weak self] capture.
      When the closure is done executing, the strong reference will be cleared
      and only the weak reference will be held on behalf of the closure.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#non-self-references>
Non-self References

Because guard is an additional capture type, like weak and unowned, it
can also be used to capture references other than self:

let capturingTwo = { [guard self, button] in
    // weakly capture self and button
    // but execute the closure with strong references
    // if and only if self AND button still exist
    // when the closure is being asked to execute
}

When encountering multiple references being captured via guard, the
closure will execute *only* when *all* references are still alive when
the closure is being asked to execute.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#limitations>
Limitations

Because guard is a special capture type that causes the closure to become
a no-op once a referenced object deallocates, it is only designed to be
used with closures returning Void.

This limitation was deemed acceptable because it would cover the vast
majority of cases, and those that it didn't cover can still fall back on
the existing technique.

The compiler should emit an error if this notation is used in conjunction
with a closure that has a non-Void return type.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#caveats>Caveats

This notation is not intended to be a full-fledged replacement for guard
statements within the closure. We are only using guard here as a way to
declare a specific memory-management behavior for references. Therefore,
guard within [square brackets ] should be seen as a capture type on par
with weak or unowned.

Unlike with a typical guard statement, we are not attempting to support
an else or where clause, or any boolean expressions within this notation.

Rather, we're simply adding a new capture behavior and providing a means
to specify an early exit if the behavior couldn't be fulfilled because one
or more objects was deallocated.

The word guard was chosen as the capture type because (1) it functions as
a guard, ensuring that the closure doesn't execute unless the specified
objects are still alive, and (2) it obviates the need for the full-fledged
guard statement that would otherwise be required to achieve the same
result.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#impact-on-existing-code>Impact
on Existing Code

None, since this does not affect any existing constructs. Implementation
of this proposal will not result in any code breakage.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#alternatives-considered>Alternatives
Considered
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#status-quo>Status
Quo

The primary alternative is to do nothing, requiring developers to add
boilerplate guard code and handle upgrading the weak-to-strong references
manually.

As stated above, this leads to needless boilerplate that can easily be
factored out by the compiler. Also, the use of a self-like variable with
an arbitrary name makes it more difficult to exhaustively find such uses in
large projects. With this proposal, searching for the text "[guard" is
all that's necessary to find all instances of this memory management
technique.

Finally, the need to declare and use alternate names to capture values
that already have existing names adds visual clutter to code and serves to
obscure the code's original intent, making it harder to reason about.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-an-optional-return-type>Closures
with an Optional Return Type

One possible addition to this proposal would extend support to any closure
returning an Optional of some kind.

If one of the objects in a guard capture list has been deallocated,
executing the closure will always result in an immediate nil return.

This idea was excluded from this iteration of the proposal due to a
concern that it relied on a "magic return value" (albeit a reasonable one)
and a perception that the community favored a solution with a smaller
conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-a-bool-return-type>Closures
with a Bool Return Type

One possible addition to this proposal would extend support to any closure
returning a Bool.

If one of the objects in a guard capture list has been deallocated,
executing the closure will always result in an immediate false return.

This idea was excluded from this iteration of the proposal due to a
concern that it relied on a "magic return value" (albeit a reasonable one)
and a perception that the community favored a solution with a smaller
conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-arbitrary-return-types>Closures
with Arbitrary Return Types

An earlier iteration of this proposal included support for closures with
arbitrary return values. The community consensus was that the proposal was
too heavy-weight and tried to do too much, and would lead to verbosity
within the capture declaration. As a result, this idea was removed from the
proposal.

The ability to handle non-Void return values relied on supporting an else clause
within a guard-based capture list:

let happinessLevel: () -> Int = { [guard self else -1] in
    var level = 0
    level += self.isHealthy ? 25 : 0
    level += !self.isHungry ? 25 : 0
    level += !self.isFearful ? 25 : 0
    level += self.hasLove ? 25 : 0
    return level
}

Here, the else clause provides a value to return in cases where self has
gone away and the guard fails.

In this example, if you call happinessLevel() after self has been
deallocated, the value -1 will always be returned.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#citations>Citations

Variations on this proposal were discussed earlier in the following
swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
threads:

   - Wanted: syntactic sugar for [weak self] callbacks
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/008713.html>
   - Allowing guard let self = self else { … } for weakly captured self
   in a closure.
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009023.html>
   - [Draft Proposal] A simplified notation for avoiding the weak/strong
   dance with closure capture lists
   <https://lists.swift.org/pipermail/swift-evolution/Week-of-Mon-20160201/009241.html>

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


(Andrew Bennett) #7

Looks great, I support this as-is, I do have one suggestion, perhaps for a
follow up proposal:

Have a protocol akin to NilLiteralConvertible, let's call it
GuardFailureConvertible for now. Then have any failing guard able to
implicitly return that value. The protocol is something like this:

protocol GuardFailureConvertible {
    init(guardFailure: Void)
}

Your guard syntax could go unchanged, normal guards could also have a
shorthand:

guard something() else return

···

On Saturday, 13 February 2016, Evan Maloney via swift-evolution < swift-evolution@swift.org> wrote:

Hello,

I sent out an earlier draft of this proposal last week, and have updated
it based on feedback from the list.

The newer proposal now has a much smaller conceptual footprint to address
concerns that the previous iteration was too complex and would lead to
verbose notation within the capture lists.

You can find the updated gist here
<https://gist.github.com/emaloney/d34ac9b134ece7c60440>, as well as
pasted below.

Evan

---

Simplified notation for avoiding the [weak self]/strongSelf dance with
closures

   - Proposal: TBD
   - Author: Evan Maloney <https://github.com/emaloney>
   - Status: *Draft*
   - Review manager: TBD

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#introduction>
Introduction

Frequently, closures are used as completion callbacks for asynchronous
operations, such as when dealing with network requests. It is quite common
to model these sorts of operations in such a way that an object instance
represents a request/response transaction, for example:

protocol NetworkTransaction: class
{
    enum Result {
        case Succeeded(NSData)
        case Failed(ErrorType)
    }

    func execute(completion: (Result) -> Void)
}

Here, the NetworkTransaction protocol declares the interface by which an
asynchronous transaction occurs. The user of a NetworkTransaction calls
the execute() function, passing in a completion function that is called
at some time in the future, when the transaction completes.

For example, imagine a hypothetical DataConsumer class that uses a
transaction to try to fetch some network data and process it:

class DataConsumer
{
    let transaction: NetworkTransaction

    init(transaction: NetworkTransaction)
    {
        self.transaction = transaction
    }

    func fetchData()
    {
        transaction.execute() { [weak self] result in
            guard let strongSelf = self else {
                return
            }

            switch result {
            case .Succeeded(let data):
                strongSelf.processData(data)

            case .Failed(let err):
                strongSelf.handleError(err)
            }
        }
    }

    func processData(data: NSData)
    {
        // process the data
    }

    func handleError(error: ErrorType)
    {
        // handle the error
    }
}

You'll notice the [weak self]/strongSelf dance in the fetchData() function.
This is a common pattern with asynchronously-executed closures, and it
signals the possibility that a closure might outlive its usefulness.

Because the NetworkTransaction may complete at any time, it is possible
that the closure will execute after the DataConsumer that initiated the
transaction has been deallocated. Perhaps the user has navigated elsewhere
in the application and whatever data was to be fetched by DataConsumer is
no longer needed.

In this case, after a DataConsumer instance goes away, we don't really
want the closure doing anything. So, we capture self weakly to ensure
that the closure doesn't hold a reference to the owning DataConsumer.
That prevents a reference cycle and ensures that DataConsumer can be
deallocated when no longer in use.

When it comes time to execute the closure, the guard statement
effectively asks the question, "Is self still alive?" If the answer is
no, the guard forces a return and the rest of the closure does not execute.

If self *is* still alive, then the weakly-captured self will be non-nil and
it will be converted into a strong reference held by strongSelf for the
duration of the closure's execution.

When the closure is done executing, strongSelf goes away, once again
making the DataConsumer eligible for deallocation when no other
references are held.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#the-problem>The
Problem

The [weak self]/strongSelf dance requires common boilerplate wherever it
is used, and the fact that a self-like variable with an arbitrary name
adds noise within the closure. The more strongSelf is needed within the
closure, the more noise there is.

Further, using a consistent name like strongSelf is by convention only;
it can't be enforced by the compiler, so searching your codebase for a
given keyword won't be exhaustive if team members use the wrong name.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#proposed-solution>Proposed
Solution

The proposed solution adds a new capture type by repurposing the guard keyword
for another use, which would look like:

transaction.execute() { [guard self] result in
    switch result {
    case .Succeeded(let data):
        self.processData(data)

    case .Failed(let err):
        self.handleError(err)
    }
}

Here, the [guard self] capture list serves as a signal that the compiler
should handle the weak/strong dance itself. When encountering [guard self],
the compiler should emit code that does the following:

   - Captures self in a weak reference on behalf of the closure
   - Whenever the closure is about to be executed, the weak reference is
   checked to see if self is still alive
      - If self is not alive, the closure becomes a no-op
      <https://en.wikipedia.org/wiki/NOP>; calling the closure returns
      immediately without anything inside the braces being executed
      - If self is alive, it is upgraded to a strong reference for the
      lifetime of the closure's execution. Within the closure, self is
      non-optional, unlike how it would be with a [weak self] capture.
      When the closure is done executing, the strong reference will be cleared
      and only the weak reference will be held on behalf of the closure.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#non-self-references>
Non-self References

Because guard is an additional capture type, like weak and unowned, it
can also be used to capture references other than self:

let capturingTwo = { [guard self, button] in
    // weakly capture self and button
    // but execute the closure with strong references
    // if and only if self AND button still exist
    // when the closure is being asked to execute
}

When encountering multiple references being captured via guard, the
closure will execute *only* when *all* references are still alive when
the closure is being asked to execute.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#limitations>
Limitations

Because guard is a special capture type that causes the closure to become
a no-op once a referenced object deallocates, it is only designed to be
used with closures returning Void.

This limitation was deemed acceptable because it would cover the vast
majority of cases, and those that it didn't cover can still fall back on
the existing technique.

The compiler should emit an error if this notation is used in conjunction
with a closure that has a non-Void return type.
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#caveats>Caveats

This notation is not intended to be a full-fledged replacement for guard
statements within the closure. We are only using guard here as a way to
declare a specific memory-management behavior for references. Therefore,
guard within [square brackets ] should be seen as a capture type on par
with weak or unowned.

Unlike with a typical guard statement, we are not attempting to support
an else or where clause, or any boolean expressions within this notation.

Rather, we're simply adding a new capture behavior and providing a means
to specify an early exit if the behavior couldn't be fulfilled because one
or more objects was deallocated.

The word guard was chosen as the capture type because (1) it functions as
a guard, ensuring that the closure doesn't execute unless the specified
objects are still alive, and (2) it obviates the need for the full-fledged
guard statement that would otherwise be required to achieve the same
result.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#impact-on-existing-code>Impact
on Existing Code

None, since this does not affect any existing constructs. Implementation
of this proposal will not result in any code breakage.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#alternatives-considered>Alternatives
Considered
<https://gist.github.com/emaloney/d34ac9b134ece7c60440#status-quo>Status
Quo

The primary alternative is to do nothing, requiring developers to add
boilerplate guard code and handle upgrading the weak-to-strong references
manually.

As stated above, this leads to needless boilerplate that can easily be
factored out by the compiler. Also, the use of a self-like variable with
an arbitrary name makes it more difficult to exhaustively find such uses in
large projects. With this proposal, searching for the text "[guard" is
all that's necessary to find all instances of this memory management
technique.

Finally, the need to declare and use alternate names to capture values
that already have existing names adds visual clutter to code and serves to
obscure the code's original intent, making it harder to reason about.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-an-optional-return-type>Closures
with an Optional Return Type

One possible addition to this proposal would extend support to any closure
returning an Optional of some kind.

If one of the objects in a guard capture list has been deallocated,
executing the closure will always result in an immediate nil return.

This idea was excluded from this iteration of the proposal due to a
concern that it relied on a "magic return value" (albeit a reasonable one)
and a perception that the community favored a solution with a smaller
conceptual footprint.

<https://gist.github.com/emaloney/d34ac9b134ece7c60440#closures-with-a-bool-return-type>Closures
with a Bool Return Type


(Erica Sadun) #8

Oh, and I just realized I have been responding to the wrong thread.

[weak self]
guard reconstitute self else { return }

I thought it might be handy to control how the guard exits scope, enabling self == nil (as well as any other reconstitutions of a weak variable) failure cases to do reporting, fatalError-ing, etc. I think it's important to defer to the programmer on how the weak self condition is handled, enabling any clean-up action to be taken as well as deciding whether to terminate the app, recover, etc.

-- E, off to pick up kids

···

On Feb 12, 2016, at 3:46 PM, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

Alternatives Considered

Status Quo

Closures with an Optional Return Type

Closures with a Bool Return Type

Closures with Arbitrary Return Types

I would also discuss the alternative of allowing `self` to be shadowed so that you could write `guard let self = self else { return }`. Your proposal still has some advantages over that one, but I would like to see those explicitly called out and discussed.

--
Brent Royal-Gordon
Architechies

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


(Brent Royal-Gordon) #9

guard reconstitute self else { return }

I think `reconstitute` is inferior to `let self = ` on almost every axis. It's longer, requires you to learn a new keyword instead of applying existing constructs, and doesn't solve any other problems the way permitting `self` shadowing would.

I think it's important to defer to the programmer on how the weak self condition is handled, enabling any clean-up action to be taken as well as deciding whether to terminate the app, recover, etc.

I'm really not convinced by this by this. If you want to terminate, capture the variable `unowned`. If you want to customize the behavior in some other way, do a weak-strong dance just like today.

I'm not entirely convinced `[guard]` is a good idea either, but the lack of `else` customization isn't the reason for my concern. "Just do nothing if this doesn't exist" is by far the most common case for guarding on a weak `self`.

···

--
Brent Royal-Gordon
Architechies


(Erica Sadun) #10

I'd be fine with let self = self. My biggest issue with [guard self] is that it has to happen before any defer.

-- E

···

On Feb 12, 2016, at 4:07 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

guard reconstitute self else { return }

I think `reconstitute` is inferior to `let self = ` on almost every axis. It's longer, requires you to learn a new keyword instead of applying existing constructs, and doesn't solve any other problems the way permitting `self` shadowing would.

I think it's important to defer to the programmer on how the weak self condition is handled, enabling any clean-up action to be taken as well as deciding whether to terminate the app, recover, etc.

I'm really not convinced by this by this. If you want to terminate, capture the variable `unowned`. If you want to customize the behavior in some other way, do a weak-strong dance just like today.

I'm not entirely convinced `[guard]` is a good idea either, but the lack of `else` customization isn't the reason for my concern. "Just do nothing if this doesn't exist" is by far the most common case for guarding on a weak `self`.

--
Brent Royal-Gordon
Architechies


(Javier Soto) #11

-1

I think "return and do anything if self is nil" is a code-smell in many
cases. You're preventing a crash and a retain cycle, but at the expense of
potentially leaving some piece of functionality broken. I think often
strongly capturing self is OK if one can reason that the closure won't live
forever, or use unowned if self is guaranteed to outlive the closure.
Always resorting to weak self by default and without considering whether
it's the right choice means not thinking carefully about the lifetime
semantics of the code that you're writing, and adding syntax to make that
easier would just encourage that.

···

On Fri, Feb 12, 2016 at 3:41 PM Erica Sadun via swift-evolution < swift-evolution@swift.org> wrote:

I'd be fine with let self = self. My biggest issue with [guard self] is
that it has to happen before any defer.

-- E

> On Feb 12, 2016, at 4:07 PM, Brent Royal-Gordon <brent@architechies.com> > wrote:
>
>> guard reconstitute self else { return }
>
> I think `reconstitute` is inferior to `let self = ` on almost every
axis. It's longer, requires you to learn a new keyword instead of applying
existing constructs, and doesn't solve any other problems the way
permitting `self` shadowing would.
>
>> I think it's important to defer to the programmer on how the weak self
condition is handled, enabling any clean-up action to be taken as well as
deciding whether to terminate the app, recover, etc.
>
> I'm really not convinced by this by this. If you want to terminate,
capture the variable `unowned`. If you want to customize the behavior in
some other way, do a weak-strong dance just like today.
>
> I'm not entirely convinced `[guard]` is a good idea either, but the lack
of `else` customization isn't the reason for my concern. "Just do nothing
if this doesn't exist" is by far the most common case for guarding on a
weak `self`.
>
> --
> Brent Royal-Gordon
> Architechies
>

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

--
Javier Soto


(Brent Royal-Gordon) #12

I think "return and do anything if self is nil" is a code-smell in many cases. You're preventing a crash and a retain cycle, but at the expense of potentially leaving some piece of functionality broken.

You're breaking something unless you're not breaking anything. And I think it's actually pretty common that you're not breaking anything. For instance, if you perform an async network call to fetch the contents of a view controller and then dismiss the view controller before the fetch is complete, throwing away the results is often the best you can do. Even if you get an error, that error is probably no longer relevant.

Certainly not all blocks are like this—if you're *writing* data, you wouldn't want to throw away the result. But I think completion handlers which really can be safely ignored if the object that scheduled them has gone away are pretty common. Maybe not common enough to justify such a dramatic shorthand, but certainly common enough that I wouldn't call it a smell.

···

--
Brent Royal-Gordon
Architechies


(Taras Zakharko) #13

-1

I like the concept but the proposed solution is inflexible and way too specific. Inability to deal with return values and also inability to execute behaviour if the references have led the scope are big no-goes IMO. This is adding more arbitrary magic to the language and making it more complex.

I am still favouring the

guard self = self else {}

solution. Slightly more verbose, but infinitely more flexible and makes it very clear what is going on.

— Taras

···

On 13 Feb 2016, at 10:46, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

I think "return and do anything if self is nil" is a code-smell in many cases. You're preventing a crash and a retain cycle, but at the expense of potentially leaving some piece of functionality broken.

You're breaking something unless you're not breaking anything. And I think it's actually pretty common that you're not breaking anything. For instance, if you perform an async network call to fetch the contents of a view controller and then dismiss the view controller before the fetch is complete, throwing away the results is often the best you can do. Even if you get an error, that error is probably no longer relevant.

Certainly not all blocks are like this—if you're *writing* data, you wouldn't want to throw away the result. But I think completion handlers which really can be safely ignored if the object that scheduled them has gone away are pretty common. Maybe not common enough to justify such a dramatic shorthand, but certainly common enough that I wouldn't call it a smell.

--
Brent Royal-Gordon
Architechies

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


(Carlos Parada) #14

guard self = self else {}

I agree with Taras on this. I also would prefer something along these lines. Besides being more consistent and familiar, the else clause encourages you to think what would happen if self is gone. "Is it fine if I just return or do I have to display an error message?” This helps lessens the points Javier Soto had against the original idea.

— Carlos Parada

···

On Feb 13, 2016, at 3:07 AM, Taras Zakharko via swift-evolution <swift-evolution@swift.org> wrote:

-1

I like the concept but the proposed solution is inflexible and way too specific. Inability to deal with return values and also inability to execute behaviour if the references have led the scope are big no-goes IMO. This is adding more arbitrary magic to the language and making it more complex.

I am still favouring the

guard self = self else {}

solution. Slightly more verbose, but infinitely more flexible and makes it very clear what is going on.

— Taras

On 13 Feb 2016, at 10:46, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

I think "return and do anything if self is nil" is a code-smell in many cases. You're preventing a crash and a retain cycle, but at the expense of potentially leaving some piece of functionality broken.

You're breaking something unless you're not breaking anything. And I think it's actually pretty common that you're not breaking anything. For instance, if you perform an async network call to fetch the contents of a view controller and then dismiss the view controller before the fetch is complete, throwing away the results is often the best you can do. Even if you get an error, that error is probably no longer relevant.

Certainly not all blocks are like this—if you're *writing* data, you wouldn't want to throw away the result. But I think completion handlers which really can be safely ignored if the object that scheduled them has gone away are pretty common. Maybe not common enough to justify such a dramatic shorthand, but certainly common enough that I wouldn't call it a smell.

--
Brent Royal-Gordon
Architechies

_______________________________________________
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


(David Hart) #15

-1

Like was previously stated, I would prefer something like "guard let self = self else {}" to be valid.
It has the benefit of:

- being less surprising
- having an even smaller language footprint
- fixing all the caveats of the original proposal

···

On 13 Feb 2016, at 22:27, Carlos Parada via swift-evolution <swift-evolution@swift.org> wrote:

guard self = self else {}

I agree with Taras on this. I also would prefer something along these lines. Besides being more consistent and familiar, the else clause encourages you to think what would happen if self is gone. "Is it fine if I just return or do I have to display an error message?” This helps lessens the points Javier Soto had against the original idea.

— Carlos Parada

On Feb 13, 2016, at 3:07 AM, Taras Zakharko via swift-evolution <swift-evolution@swift.org> wrote:

-1

I like the concept but the proposed solution is inflexible and way too specific. Inability to deal with return values and also inability to execute behaviour if the references have led the scope are big no-goes IMO. This is adding more arbitrary magic to the language and making it more complex.

I am still favouring the

guard self = self else {}

solution. Slightly more verbose, but infinitely more flexible and makes it very clear what is going on.

— Taras

On 13 Feb 2016, at 10:46, Brent Royal-Gordon via swift-evolution <swift-evolution@swift.org> wrote:

I think "return and do anything if self is nil" is a code-smell in many cases. You're preventing a crash and a retain cycle, but at the expense of potentially leaving some piece of functionality broken.

You're breaking something unless you're not breaking anything. And I think it's actually pretty common that you're not breaking anything. For instance, if you perform an async network call to fetch the contents of a view controller and then dismiss the view controller before the fetch is complete, throwing away the results is often the best you can do. Even if you get an error, that error is probably no longer relevant.

Certainly not all blocks are like this—if you're *writing* data, you wouldn't want to throw away the result. But I think completion handlers which really can be safely ignored if the object that scheduled them has gone away are pretty common. Maybe not common enough to justify such a dramatic shorthand, but certainly common enough that I wouldn't call it a smell.

--
Brent Royal-Gordon
Architechies

_______________________________________________
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