`defer` in async Task & actor isolation

Under Swift, the typical paradigm of try-catch-finally has been replaced with do-defer-catch, where defer plays the role of finally, ensuring code is executed regardless of the error handling path.

It seems that in an asynchronous environment within Swift Concurrency, this paradigm may break down:

struct SandView: View {
  @State private var progress: Progress?

  var body: some View {
    Button("Sand Box") {
      Task {
        do {
          self.progress = .init()
          defer { self.progress = nil } // property 'progress' isolated to global actor 'MainActor' can not be mutated from a non-isolated context

          try await withCheckedThrowingContinuation { $0.resume() }
        } catch {
          print("uh oh: \(error)")
        }
      }
    }
  }
}

Is there a solution to this or an alternative paradigm that can be recommended?

1 Like

The following seems to work:

defer { Task { @MainActor in self.progress = nil } } 
1 Like

FWIW, Async, Rethrows, and Defer - #9 by Joe_Groff is proposing an async defer, I hope it'll get into a pitch eventually.

2 Likes

TBC, I am not personally actively working on a proposal for this, so if community folks are interested in proposing it, don't let me stop you.

Note that there was a bug where defer was incorrectly being considered to have a different isolation from the surrounding function. That bug should be fixed, but let us know if you're still seeing it.

7 Likes