3
$\begingroup$

Reference counting works best if there are no cycles. But closures that allow access to every variable in the parent scope by default automatically create cycles.

There are obvious solutions like only importing actually used or previously defined variables. There are also more advanced solutions like considering it like a member method and extend the lifetime of the parent only in that way i.e. using the same reference counter as the parent scope.

But I want to know the possibilities and varieties, and see whether there are more innovative ways.

$\endgroup$
18
  • 4
    $\begingroup$ I think generally, regardless of the problem of reference cycles, any implementation of closures which cares about avoiding memory leaks will not retain local variables that aren't closed over; even if those references are cyclic, they are inaccessible, and retaining them would cause some garbage to be uncollectible. So what you describe as "only import[ing] actually used ... variables" is something we should assume about every implementation, unless garbage collection isn't a concern at all (in which case there is no question to answer). $\endgroup$
    – kaya3
    Commented Apr 2 at 0:04
  • 4
    $\begingroup$ Besides that, you could equally ask how languages with only reference counting deal with reference cycles in other objects that aren't closures. There are some interesting answers which aren't specific to the issue of closures, but which presumably also address the problem of recursive closures. $\endgroup$
    – kaya3
    Commented Apr 2 at 0:07
  • 4
    $\begingroup$ One day a student came to Moon and said: "I understand how to make a better garbage collector. We must keep a reference count of the pointers to each cons." Moon patiently told the student the following story: "One day a student came to Moon and said: `I understand how to make a better garbage collector... $\endgroup$
    – Michael Homer
    Commented Apr 2 at 0:32
  • 2
    $\begingroup$ @kaya3: Mutable bindings can lead to cycles that refcounting has problems with -- there's nothing special or different about closures. Absent mutable bindings, the newly created closure is bound in a new scope, not in the parent scope, so there's no cycles. So if you don't allow mutable bindings and dont allow recursive binding (no letrec equivalent), then there are no cycles and closures are no different from any other object. Even with letrec, you can handle that specially (basically, a single refcount for all the recusively bound things) and avoid cycles that way. $\endgroup$
    – Chris Dodd
    Commented Apr 2 at 3:40
  • 1
    $\begingroup$ @kaya3: That's an example of a letrec (even though it does not use that keyword), as the definition of fib used in the binding is recursive, not the definition from the containing scope. Javascript, like many langauges, makes all bindings implicitly letrec. $\endgroup$
    – Chris Dodd
    Commented Apr 2 at 3:45

1 Answer 1

3
$\begingroup$

Swift

Explicit capture lists

Swift closures can have explicit capture lists, which allow them to weakly capture variables to prevent strong references. By default, any variable referenced by a closure which is not in its capture list is strongly captured.

let foo: Foo
let bar: Bar
let baz: Baz
let qux: Qux

func returnAClosure() -> () -> Void {
    return { [foo, weak bar, unowned baz] in
        // `foo` is strongly captured.
        // It won't be deallocated as long as this closure is referenced somewhere.
        foo.doSomething()
        // `bar` is safely weakly captured.
        // Its type within the closure is nullable.
        // It can be deallocated while this closure is referenced, and if so, within the closure it will be set to `null`.
        bar?.doSomethingElse()
        // `baz` is unsafely weakly captured.
        // It can be deallocated while this closure is referenced, and if so, it will point to garbage data, causing undefined behavior if the closure is invoked.
        baz.doSomethingElse2()
        // Variables not explicitly specified in capture lists default to being strongly captured.
        // So `qux` is strongly captured like `foo`.
        // Note that, while identical when capturing reference types,
        //   an explicit capture and implicit capture have different behavior when capturing value types like `struct`s (see the official Swift documentation).
        qux.doSomethingElse3()
    }
}

See the official Swift documentation (2) and this article for more info.

Make all captured variables explicit

Swift also has a special rule that escaping closures can't reference the methods or properties of self without explicitly qualifying them or including self in the explicit capture list. Escaping closures are those that may continue to exist after the scope they are bound in (e.g. are assigned to a field or global variable or returned by a function); non-escaping closures can't create strong cycles, so this special rule doesn't apply to them. A subtle problem that arises with escaping closures is that, without this special rule, they could capture self without literally referring to self, since ordinarily Swift allows a class to refer to its own methods and properties without the self qualifier. The rule effectively makes it so a closure will only directly capture variables which are literally referenced somewhere in its body, although those variables may still have their own references which the closure transitively upholds.

The rule doesn't do anything more than require the programmer to literally include self in the closure; it doesn't change Swift's semantics or expressiveness, and doesn't actually prevent strong cycles between self and the closure. Presumably it's an attempt by the language designers to make programmers more aware when they capture self, and nudge them towards weakly capturing self, while still allowing them to strongly capture if necessary. I can't speak towards what motivated the Swift developers to include the rule, or its effectiveness at preventing strong reference cycles vs the amount of strong reference cycles involving closures that remain in Swift code.

See the "Escaping Closures" section in the official Swift documentation for more info.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .