Request explicit capture of self instead of defaulting to a strong capture of self

Summary (TLDR)

I suggest compiler warning is added to all closures that may escape and captures self without explicitly declaring if they wish to capture self weakly or strongly.

To avoid the warning the developer must be explicit; do you wish to strongly capture self (use [self]) or weakly capture self (use [weak self]).

By making it a compiler warning we get to keep source compatibility, and there's no need to introduce new syntax for strongly capturing self since [self] will do that today.

Today the behavior is that if you reference self in a closure, without explicitly specifying [weak self] in the capture list, it will implicitly strongly retain self and thus you might accidentally create a reference cycle (or memory bloat). I argue that the current default (implicit) behavior is default error-prone, but doesn't have to be.

Here's some example code, to hopefully explain what I mean:

class OurTestSubject {
    typealias ArbitraryFunction = () -> Void

    init(name: String) {
        self.name = name
    }

    let name: String

    // A function that we can return down below, that captures self strongly.
    private func printer() {
        print(name)
    }

    // Case 1.
    //
    // This will not compile today, due to the following compiler error:
    //
    // - error: Reference to property 'name' in closure requires explicit 'self.' to make capture semantics explicit
    //
    // Auto fix-it here is to prefix the access, so it becomes `self.name`, but
    // this might create a reference cycle or memory bloat, so I suggest it should
    // force you to pick one of two options:
    //
    // - a (case 3): return { [self] in print(self.name) }
    // - b (case 4): return { [weak self] in print(self?.name ?? "too late") }
    func shouldNotCompile() -> ArbitraryFunction {
        return { print(name) }
    }

    // Case 2.
    //
    // Today this compiles without issue. Because this will implicitly strongly
    // capture self, we might accidentally create a retain cycle here.
    //
    // PITCH: make this a compiler warning, requesting the developer to be explicit
    // in if they wish to [self] or [weak self].
    func shouldCompileButWithAWarning() -> ArbitraryFunction {
        return { print(self.name) }
    }

    // Case 3.
    //
    // Explicit strong capture of self, we're OK with this, no warning.
    func explicitlyStrongSoCompilesWithoutWarning() -> ArbitraryFunction {
        return { [self] in print(self.name) }
    }

    // Case 4.
    //
    // Explicit weak capture of self, we're OK with this, no warning.
    func explicitlyWeakSoCompilesWithoutWarning() -> ArbitraryFunction {
        return { [weak self] in print(self?.name ?? "too late") }
    }

    // Case 5.
    //
    // I assume this is a hard case to handle separately, so no change here.
    func returnsAFunctionThatStronglyRetainsSelf() -> ArbitraryFunction {
        return printer
    }
}

I noticed a similar discussion going on in Disable capturing strong self in closures — I was unsure if this pitch would be off-topic or not so I opted for creating a new topic. Might be worth merging the two discussions?

3 Likes

+1
I would appreciate something that will force me to think about how I capture self in closures that can escape. Requiring writing self was supposed to do that, but I always write self to differentiate properties from local variables.