Targeting a specific scope with defer

Is there a way to specify which scope a deferred block should run at the end of, rather than just the current scope?

e.g. I might want to write:

var data: UnsafeMutablePointer<UInt8>

if someCondition {
    data = someNonOwnedResult
} else {
    data = someOwnedResult
    defer { data.deallocate() } // 🚨 Runs too early.

    try … // More work here contingent on !someCondition.
}

try doSomething(with: data)

// *Now* I want to deallocate `data` (if necessary).

I want that defer to run at the end of the outer scope (typically the function's full scope, but not always), but as far as I can tell there's no elegant way to do that today?

Typically I end up with something like:

var data: UnsafeMutablePointer<UInt8>
var deallocateData = false

let condition = someCondition

if condition {
    data = someNonOwnedResult
} else {
    data = someOwnedResult
    deallocateData = true
}

defer {
    if deallocateData {
        data.deallocate()
    }
}

if !condition {
    try … // Finish doing all the other !someCondition stuff.
}

try doSomething(with: data)

Even in this contrivedly-trivial case that's pretty unappealing, let-alone in real-world code with multiple levels of indentation (when data is populated and its deallocation requirement determined) and with much more stuff in-between the key points.

I feel like it'd be quite helpful if defers could be targeted to labels, including an implicit label for the enclosing function, e.g.:

var data: UnsafeMutablePointer<UInt8>

if someCondition {
    data = someNonOwnedResult
} else {
    data = someOwnedResult
    defer to function exit { data.deallocate() }

    try … // More work here contingent on !someCondition.
}

try doSomething(with: data)

I know there's other workarounds like making a wrapper type for the object & the flag of whether it should be deleted, and handling that in object deinit, but that's conceptually heavy-weight for such a simple task. (made better, semantically, with non-copyable types so that it can be a struct, but even so)

1 Like

I guess just use a function:

if someCondition {
    try doSomething(with: data)
} else {
    defer { someOwnedResult.deallocate() }
    try doSomething(with: someOwnedResult)
}

A bit clumsy, but more explicit and likely easier to read (to me, at least).

Whenever I find myself wishing for a break n / continue n statement, I tend to refactor it into a function with early returns. Both situations are more or less gotos and, as you mention...

... are quite difficult to follow

Alternatively, you could save one level of indentation with:

guard !someCondition else { return try doSomething(with: data) }

defer { deallocate() }
try … // More work here contingent on !someCondition.
try doSomething(with: data)

Late to this discussion, but I've just come across it in a search after having to do a clumsy workaround for Swift's limited defer. Every option makes my code suck.

This should be another use for labelled control flow statements, they get so little love already. New alternate forms of defer should be:

defer outer {
    // runs not when the closest scope ends but rather the one labelled with "outer"
}

and this special case that uses the func keyword in place of a label:

defer func {
    // runs not when the closest scope ends but rather the enclosing function
}

Is there anyone interested and able to help me pitch this to Swift Evolution?