Closure isolation capture seemingly can't see through aliases – expected or a bug?

closures can currently be isolated in two ways:

  1. via a gobal-actor-isolated type annotation
  2. via a capture of an isolated parameter

for the second case, i was a bit surprised to find that certain forms of isolated parameter capture don't appear to 'count' for the purposes of this isolation determination. e.g. this works

func test_captures(isolation: isolated any Actor) async {
  await withTaskCancellationHandler {
    isolation.assertIsolated() // ✅ - isolation captured from outer scope
  } onCancel: {}
}

but this surprisingly does not:

func test_captures(isolation: isolated any Actor) async {
  await withTaskCancellationHandler { [isolation] in
    isolation.assertIsolated() // 🛑 - isolation aliased via capture list entry
  } onCancel: {}
}

is this behavior expected?

4 Likes

incidentally stumbled upon the 'Generalized isolation checking' section of SE-0420, which states (emphasis mine):

According to SE-0304, closures passed directly to the Task initializer (i.e. Task { /*here*/ }) inherit the statically-specified isolation of the current context if:

  • the current context is non-isolated,
  • the current context is isolated to a global actor, or
  • the current context has an isolated parameter (including the implicit self of an actor method) and that parameter is strongly captured by the closure.

The third clause is modified by this proposal to say that isolation is also inherited if a non-optional binding of an isolated parameter is captured by the closure. A non-optional binding of an isolated parameter is defined in the generalized isolation checking section.

...

An expression is a non-optional derivation of an isolated parameter param if it is:

  • param? (the optional-chaining operator);
  • param! (the force-unwrapping operator); or
  • a reference to a non-optional binding of param, i.e. a let constant initialized by a successful pattern-match which removes the optionality from param, such as ref in if let ref = param.

this suggests to me that a capture list binding of the isolated parameter should still 'count' as the closure's capturing the isolated parameter.

is this the correct conclusion to draw here @hborla @John_McCall @Douglas_Gregor?

I think we may not have fully implemented that, which at this point implies that it should be pared down to match the implementation.