[swift-evolution] [Draft] @selfsafe: a new way to avoid reference cycles

Matthew Johnson matthew at anandabits.com
Sat Feb 18 19:24:25 CST 2017


# `@selfsafe`: a new way to avoid reference cycles

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

## Introduction

This proposal introduces the `@selfsafe` function argument attribute which together with a `withWeakSelf` property on values of function type.  Together these features enable library authors to create APIs can be statically verified to never extend the lifetime of the `self` a function they take may have captured.  This is accomplished by allowing the library implementation convert the function to a nearly identical function that is guaraneteed to have a `weak` capture of `self` and be a no-op after `self` is released.

Swift-evolution thread: []()

## Motivation

Accidentally forgeting to use weak references is a common problem and can easily lead to reference cycles.  Some APIs are best designed such that users *cannot* extend the lifetime of `self` by escaping a closure that happens to strongly capture `self`.  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:

```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:

```swift
// 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 captured weakly falls on users of the library.

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

## Proposed Solution

This proposal introduces a read-only property on all function types: `withWeakSelf` as well as a `@selfsafe` function argument annotation.  (This name for the annotation is a strawman - I would love to hear better ideas)

### `withWeakSelf`

`withWeakSelf` can be imagined as a property declared like the following:

```swift
extension T -> Void {
  var withWeakSelf: T -> Void { return // compiler magic }
}
extension T -> U {
  var withWeakSelf: T -> U? { return // compiler magic }
}
extension T -> U? {
  var withWeakSelf: T -> U? { return // compiler magic }
}
```

It returns a closure that is identical to itself in every respect except four:
1. If the context includes a strong or unowned `self` capture, that is converted to a weak capture.
2. If the function returns a non-`Void`, non-`Optional` type the return type is wrapped in an `Optional`.  
3. The function returned by `withWeakSelf` is a no-op after `self` has been released.  If the return type is non-`Void`, the function returns `nil` after `self` has been released.
4. Any addtional non-`self` context is released as soon as possible after `self` is released.

From these rules, it follows that `withWeakSelf` can be a no-op unless `self` is captured strong or unowned or the return type is non-`Void` and non-`Optional`.

Important note: any additional context the closure requies beyond `self` continues to be captured as it was originally.

### `@selfsafe`

When a function argument is annotated with `@selfsafe` two rules are in effect for the implementation:
1. The argument *is not* allowed to escape the function.
2. The closure returend by `withWeakSelf` *is* allowed to escape the function.

There should also be a warning if `argument.withWeakSelf` *does not* escape the function in any code paths (because the `@selfsafe` annotation is meaningless if it does not escape).

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

```swift
   class UIControl {
   
      var action: () -> Void
   
      func setAction(_ action: @selfsafe () -> Void) {
         self.action = action.withWeakSelf
      }
   }
```

In the previous example the function declares the argument with the `@selfsafe` annotation.  The compiler verifies that the `action` argument does not escape `setAction` and that `action.withWeakSelf` *does* escape.  Because these verifications guarantee that the lifetime of `self` cannot be exteneded by calling `setAction`, the compiler can allow users to omit `self` if this function is called with a closure.

```swift
class UIViewController {
   func viewDidLoad() {
       // misc setup
       myControl.setAction(doSomething) // great! no problem creating a reference cycle.
       myOtherControl.setAction {
          // `self.doSomethingElse()` is not required.
          // The compiler knows we cannot create a reference cycle here.
          doSomethingElse()
       }
   }
   func doSomething() {}
   func doSomethingElse() {}
}
```

## Detailed design

TBD - If anyone can think of additional details that need to be spelled out please provide feedback in this thread.

## Source compatibility

No effect.  This is a purely additive change.

## Effect on ABI stability

No effect.  This is a purely additive change.

## Effect on API resilience

No effect.  This is a purely additive change.


## Alternatives considered

I considered various ways of exposing the entire captured context or the captured `self` reference directly as properties of function types, along with access to an unbound version of the function.  This turns out to be far more complex than necessary to solve the problem this proposal aims to solve and doesn't enable any interesting use cases I have thought of.



More information about the swift-evolution mailing list