If I understand you correctly, those are basically two questions, so first:
- What do I mean with "isolated to the same context"? What's "context" here?
"Context" in a way means "the surrounding code" or "the function/scope the closure is defined in". A function you define on an actor is, naturally, in the isolation domain that is the actor. So if you then, in the implementation of that function, define a "regular old closure", it has the same isolation, i.e. also the actor.
It may help a little to think of as "non-isolated" to be "yet another isolation domain". it's the isolation domain of "not being isolated to anything specific" or "free to be used from any other isolation domain". So if I say that a closure is "isolated to the same context [as something else]", I just mean "whatever isolation domain this context belongs to, the closure has the same thing".
Also note that this does indeed happen at compile time, so wherever you write the actual source for a closure, just check which isolation the code before/around it has and you know what the closure is isolated to (which may, again, also be non-isolated).
- " does non-isolated mean that these rules don’t apply in a way?"
Well, when we're speaking of closure definition in actor definitions, yes. The reason why any@Sendableclosure you define in an actor (like in the proposal's example withTask.detached) is indeed interpreted differently (i.e. it does not have the same isolation as its surrounding context, i.e. the actor) is simply that it doesn't make sense to treat it the same as a non-@Sendableclosure. The only reason why functions likeTask.detachedrequire a@Sendableclosure in the first place is that they want to run the closure in a different isolation that the context where the closure is defined. As the example shows, this means that when you're inside the closure and want to access actor state, you have to await it.
Basically the entire section of the proposal explains that "Hey, look, this is how you can isolate state and run code in a concurrent world. But careful, when you somehow explicitly leave your own isolated island, you're obviously no longer on your own island."[1]
Before we had structured concurrency in the language syntax, everything basically always had the same isolation as the surrounding code: non whatsoever (syntax wise). You could always (synchronously) refer to stuff in a closure that was actually defined outside of it. However, when the closure ran in a different thread (let's say you pass the closure to DispatchQueue.main.async, this was actually an invisible problem:
class HurtMe {
var someValue: String?
func pain() {
DispatchQueue.main.async {
// imagine something complicated here, etc.
self.someValue = "..."
}
}
The compiler doesn't somehow "judge" the closure I define and send to async. There was no way to express this and it doesn't care that I could also modify someValue while the queue is running the closure. With structured concurrency, everything is somehow marked during compile time about "where" it will run. Either implicitly or explicitly.
And in the case of @Sendable closures defined in an actor-isolated context (i.e. any "code block" or scope) the new syntax's meaning is that it is associated to the non-isolated "where", unlike non-@Sendable closures[2]