[swift-evolution] [Draft] @selfsafe: a new way to avoid reference cycles
Daniel Duan
daniel at duan.org
Sat Feb 18 21:59:27 CST 2017
This reminded me of an idea I had long time ago which will have a similar effect: add a way to disable implicit captures for closures. FWIW.
> On Feb 18, 2017, at 5:24 PM, Matthew Johnson via swift-evolution <swift-evolution at swift.org> wrote:
>
> # `@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.
>
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
More information about the swift-evolution
mailing list