Isolated(any) evolution and current limitations

Hello there!

I’m working as a platform engineer on a testing framework and i’m hitting some limitation with isolation and isolated(any). I’d like to give some examples and ask a few questions here.

I have been using @isolated(any) to make a mocking framework that can handle both actor-isolated and non-isolated closures in a uniform way.

Right now, calling a closure typed as @isolated(any) synchronously is allowed, but it started giving a warning.


I have extracted a small part of the testing framework here GitHub - SergeyPetrachkov/IsolatedAnySampleProj . The idea is to use generics to make it easier to mock protocols.

MockFunc represents a container that observes and records a function behavior. So, if we have a protocol that has a function, and we want to know how many times the function was called and with what arguments, we use MockFunc<Input, Output>.

In Tests target you can find an example of how a protocol is mocked and how the framework is used in tests.

The most important part of MockFunc is that it helps developers to flatten out async tests for the closure-based APIs.

Problem

With introduction of Swift Concurrency we've got a concept of isolation and isolation domains, which can be applied not only to types, but also to closures, so it is possible to have a function like this:

func loadData(completion: @MainActor @escaping (Data) -> Void) {}

and it will be a different function than:

func loadData(completion: @escaping (Data) -> Void) {}

(It's out of scope whether or not such APIs should exist at all)

To be able to test and to mock both types of functions, I assumed that isolated(any) could be a nice fit. So, the MockFunc has this:

public func callAndReturn(
		_ input: Input,
		completion: @escaping @isolated(any) (Output) -> Void
	) {
		call(with: input)
		let completionContainer = CompletionContainer(completion: completion)
		completions.append(completionContainer)
		if callsCompletionImmediately {
			completionContainer(output)
		}
}

while the CompletionContainer looks like this:

public struct CompletionContainer<Output> {

	let completion: (Output) -> Void

	@inline(__always)
	public func callAsFunction(_ output: Output) {
		completion(output)
	}
}

An example of a mock:

final class MockImageLoader: ImageLoader, @unchecked Sendable {

	typealias LoadUrlItemCompletionInput = (
		url: URL,
		item: ImageCacheItem
	)

	let loadUrlWithCompletionMock = MockFunc<LoadUrlItemCompletionInput, (ImageCacheItem, Image?)>()
	func load(url: URL, item: ImageCacheItem, completion: @escaping @MainActor @Sendable (ImageCacheItem, Image?) -> Void) {
		loadUrlWithCompletionMock.callAndReturn((url, item), completion: completion)
	}

	let loadUrlWithNonIsolatedCompletionMock = MockFunc<LoadUrlItemCompletionInput, (ImageCacheItem, Image?)>()
	func loadNonIsolated(url: URL, item: ImageCacheItem, nonIsolatedCompletion: @escaping @Sendable (ImageCacheItem, Image?) -> Void) {
		loadUrlWithNonIsolatedCompletionMock.callAndReturn((url, item), completion: nonIsolatedCompletion)
	}
}

An example of a test:

	@Test(.testEnvironment)
	@MainActor
	func regularMockFuncWithCompletionGetsCalled() {
		let env = Environment.current
		let sut = env.makeSUT()

		var completionCallsCount = 0
		// we actually prove that MockFunc flattens async completions to sync code,
		// as the checks within this closure will be done synchronously before the test finishes
		let completion: @MainActor @Sendable (ImageCacheItem, Image?) -> Void = { cacheItem, image in
			completionCallsCount += 1
			#expect(cacheItem == env.sampleCachedImage)
			#expect(image == env.sampleImage)
		}

		sut.load(url: env.sampleURL, item: env.sampleCachedImage, completion: completion)

		#expect(sut.loadUrlWithCompletionMock.called)
		#expect(sut.loadUrlWithCompletionMock.calledOnce)
		#expect(sut.loadUrlWithCompletionMock.input == (url: env.sampleURL, item: env.sampleCachedImage))
		#expect(sut.loadUrlWithCompletionMock.output == (env.sampleCachedImage, env.sampleImage))
		#expect(sut.loadUrlWithCompletionMock.completions.count == 1)
		#expect(completionCallsCount == 1)
	}

In Swift 6.2 it gives the following warning:

Converting @isolated(any) function of type '@isolated(any) (Output) -> Void' to synchronous function type '(Output) -> Void' is not allowed; this will be an error in a future Swift language mode

If I check the source codes of Swift, it looks like the isolation becomes a part of a type, which is ok, but introduces some limitations.

Right now the code compiles and allows to execute the completions synchronously, but I guess this will break in Swift 6.3 or Swift 7.

Since, it's a testing framework and synchronous execution is crucial, I thought we could use the same as MainActor.assumeIsolated, because we have the isolation property like this:

let actor = completion.isolation
actor?.assumeIsolated { _ in
    completion(output)
}

while I understand this is unsafe in production, in testing scenarios the tradeoff may be acceptable

Unfortunately, it's not possible. It gives the following error:
Call to @isolated(any) parameter 'completion' in a synchronous actor-isolated context

which is conceptually correct, but as with MainActor.assumeIsolated I expected this to be a solution to execute potentially unsafe code.


So, I have a few questions:

  • will we get a way to assumeIsolated the same way we have now with the MainActor? Or is this limitation intentional?

  • is there any other way to achieve my goal (synchronous execution of closures regardless of isolation) besides creating another GlobalActorIsolatedMockFunc type per global actor in my project?

2 Likes

I admit that I don't quite understand what you're asking, and I find your example hard to follow because the code isn't self-contained, so we can't play around with it (edit: sorry @SergeyPetrachkov, this was uncalled for. I overlooked that you provided a full sample project and not just the code snippets in your post).

But I can answer this bit: you can often use an intermediate local nonisolated(unsafe) variable for a local opt-out from strict concurrency checking.

For example, this code compiles (and I think it roughly follows what you want to do in your example):

import Dispatch

@MainActor
func f(_ isolatedAnyFunc: @escaping @isolated(any) () -> Void) {
    // Assign to local nonisolated(unsafe) var to opt out of concurrency checking.
    // Warning in Swift 6 mode: Converting @isolated(any) function of type '@isolated(any) () -> Void' to synchronous function type '() -> Void' is not allowed; this will be an error in a future Swift language mode
    nonisolated(unsafe) let unsafeSyncFunc: () -> Void = isolatedAnyFunc
    unsafeSyncFunc()
}

@MainActor func mainActorFunc() {
    MainActor.preconditionIsolated()
    print("Hello from MainActor")
}

nonisolated func nonIsolatedFunc() {
    dispatchPrecondition(condition: .notOnQueue(.main))
    print("Hello from non-isolated function")
}

// Calling f() from `main`, i.e. in a MainActor context.

// Works
f(mainActorFunc)

// 💥 Crash here because precondition in nonIsolatedFunc is violated.
f(nonIsolatedFunc)

This code runs until you call f(nonIsolatedFunc) and then crashes because we're violating the dispatchPrecondition inside nonIsolatedFunc. As expected, because this is what you get when using an unsafe opt-out.

I ran this in an Xcode playground with Swift 6.2 beta in Swift 6 language mode. The calls to f() happen in a MainActor context.

1 Like

Thanks for thinking along!

My goal is to get away from the warning about the conversion of an isolated function to a nonisolated one, as I want to have a future-proof solution.

So, I have a property that stores closures (nonisolated), I have a function that accepts isolated(any) closures. I want to place an argument of a function into a property.
In swift 6.2 it started giving a warning because isolation becomes a part of the type.
Then I want to be able to call the closure synchronously without awaiting anything (same as I can call MainActor.assumeIsolated).

So, I have the isolation property, but I can’t use it to run assumeIsolated on that

Yes, I got that. I don't have an answer for this.

As far as I can tell, my "solution" (assigning to a local nonisolated(unsafe) variable) silences the compiler error you're getting, so it could be a temporary solution. It won't silence the warning though, and (as you know) it’s very much unsafe.

For me it’s still giving the same error even with nonisolated(unsafe), I’m using Xcode 26 beta 7

This compiles for me (Xcode 26 beta 5, Swift 6 mode):

func f(_ isolatedAnyFunc: @escaping @isolated(any) () -> Void) {
    // Warning in Swift 6 mode: Converting @isolated(any) function of type '@isolated(any) () -> Void' to synchronous function type '() -> Void' is not allowed; this will be an error in a future Swift language mode
    nonisolated(unsafe) let unsafeSyncFunc: () -> Void = isolatedAnyFunc
    if let actor = isolatedAnyFunc.isolation {
        print("isolation: \(actor)")
        actor.assumeIsolated { _ in
            unsafeSyncFunc()
        }
    } else {
        print("isolation == nil")
        unsafeSyncFunc()
    }
}

Something like this is what you were trying to do, isn't it?

Yes, something like this except for I want to get rid of the warning. It compiles now, but will not compile in future. So I want to either change the way it’s organized and remove the isolation completely (limiting the usecases of my utility), or see if it’s something that will appear in future versions of Swift.

So, what I have now is working fine. My concern is the warning and I want to build a future proof solution

as the one that added this warning (:see_no_evil_monkey:) i'll take a stab at offering a solution. but first, as you pointed out, the fact that the compiler warns when performing a conversion that drops the isolation information here is intentional – that it did not produce any diagnostic in the past was a bug. this diagnostic should presumably have always existed and been an error in swift-version 6 as it can introduce potential data races, but is currently only a warning so as to not break existing code.

as with most things in swift, if you want to convince the compiler of something that undermines its goals (or it's not currently 'smart enough' to realize), you usually need to use the word unsafe somewhere. in this case, one possibility may be to add a way to 'factor out' the actor isolation from the function value in the @isolated(any) function type via unsafe means:

// copy-paste disclaimer: not sure this is a Good Idea

enum ErasedIsolationFn<Output> {
  case nonisolated((Output) -> Void)
  case isolated (any Actor, (any Actor, Output) -> Void)

  init(
    _ fn: @escaping @isolated(any) (Output) -> Void
  ) {
    // Due to runtime requirements for interacting with @isolated(any)
    guard #available(macOS 15.0, iOS 18.0, *) else { fatalError("unsupported") }

    typealias OGFn = @isolated(any) (Output) -> Void
    typealias NoIsoFn = (Output) -> Void
    typealias IsoFn = (any Actor, Output) -> Void

    assert(MemoryLayout<NoIsoFn>.size == MemoryLayout<OGFn>.size)
    assert(MemoryLayout<IsoFn>.size == MemoryLayout<OGFn>.size)

    switch fn.isolation {
    case .none:
      let ret = unsafeBitCast(fn, to: NoIsoFn.self)
      self = .nonisolated(ret)

    case .some(let actor):
      let ret = unsafeBitCast(fn, to: IsoFn.self)
      self = .isolated(actor, ret)
    }
  }

  func callAsFunction(_ output: Output) -> Void {
    switch self {
    case .nonisolated(let fn): fn(output)
    case .isolated(let actor, let fn):
      actor.preconditionIsolated("incorrect isolation")
      return fn(actor, output)
    }
  }
}

this strategy will let you model the two cases explicitly, and seems like it might work for your purposes (the tests in your sample project passed at any rate).

however i don't have the best grasp on when or why this might break (i always find unsafeBitCast() a bit concerning). the requirement for the cast to 'work' is that the types must be 'layout-compatible', which appears like it might hold in this particular case. would certainly be interested if a more informed party could weigh in though. FWIW a similar-ish approach is used within the implementation of assumeIsolated to strip the isolated value off the invoked closure parameter – though dropping a parameter annotation does seem a bit less worrisome than adding/removing parameters.


i think if someone wanted to pitch some specific ergonomic improvements here they would probably not be too controversial. a mechanism to invoke a function with the precondition that the expected executor is present, or changes to the diagnostics to allow suppression of the warning/error when explicitly converting to a nonisolated(unsafe) variable both seem (superficially) reasonable to me.

i'm not sure of your deployment requirements (or if this would actually work), but one thing that may be worth looking into is use of the new Task.immediate API as a mechanism to allow invoking the synchronous @isolated(any) functions without the need for anything 'unsafe'. e.g. something like (various sendability changes required for compilation):

@available(iOS 26.0, *)
public struct CompletionContainer2<Output> {

  let completion: @isolated(any) @Sendable (Output) -> Void

  init(completion: @escaping @isolated(any) @Sendable (Output) -> Void) {
    self.completion = completion
  }

  @inline(__always)
  public func callAsFunction(_ output: Output) where Output: Sendable {
    completion.isolation?.preconditionIsolated("oops")
    Task.immediate { [completion] in
      await completion(output)
    }
  }
}

it might not handle the nonisolated & synchronous case correclty, but i think in theory if the executors 'line up right' the immediate Task will run, invoke the completion synchronously and return without any suspensions. anyway, something to experiment with maybe.

2 Likes

It seems to me that SE-0431: @isolated(any) Function Types lays out a future direction to allow something like what you want:

In order for a call to an @isolated(any) function to be treated as not crossing an isolation boundary, the caller must be known to have the same isolation as the function. Since the isolation of an @isolated(any) parameter is necessarily an opaque value, this would require the caller to be declared with value-specific isolation. It is currently not possible for a local function or closure to be isolated to a specific value that isn't already the isolation of the current context.[… (footnote)] The following rules lay out how @isolated(any) should interact with possible future language support for functions that are explicitly isolated to a captured value. In order to present these rules, this proposal uses the syntax currently proposed by the closure isolation control pitch, where putting isolated before a capture makes the closure isolated to that value. This should not be construed as accepting the terms of that pitch. Accepting this proposal will leave most of this section "suspended" until a feature with a similar effect is added to the language.

If f is an immutable binding of @isolated(any) function type, then a call to f does not cross an isolation boundary if the current context is isolated to a derivation of the expression f.isolation.

In the isolated captures pitch, a closure can be isolated to a specific value by using the isolated modifier on an entry in its capture list. So this question would reduce to whether that capture was initialized to a derivation of f.isolation.

[…]

For example:

func delay(operation: @isolated(any) () -> ()) {
  let isolation = operation.isolation
  Task { [isolated isolation] in // <-- tentative syntax from the isolated captures pitch
    print("waking")
    operation() // <-- does not cross an isolation barrier and so is synchronous
    print("finished")
  }
}

Admittedly, I don't understand whether this refers to exactly what you have in mind, but it sounds very similar.

1 Like

Yeah! That's something I am looking for and come across while exploring this matter. This one particularly:

For @isolated(any), we would naturally want to write this:
myFn.isolation.assumeIsolated { myFn() }
However, since Swift doesn't understand the connection between the closure's isolated parameter and myFn, this call will not work, and there is no way to make it work.

Wow, thanks @jamieQ for the reply! I saw that PR while I was exploring :slight_smile:

My deployment target is iOS 15, so unfortunately I don't have access to the latest features of the runtime. But I think I can put some efforts into upgrading the target, since in this case I'm working on the testing framework, not the production code.

I tried to play with Task.immediate, but it still suspends and jumps the contexts and I'm losing the synchronous behavior in my tests.

I kinda like the unsafe suggestion, but I'd like to hear your opinion on that one, since you have way more knowledge about the internals of Swift and it's direction: if I use the workaround you're suggesting, will this likely work up until Swift 7? I know it's a hard question to answer, but from the perspective of the Swift engineer, does it align well with the paradigm of the language and unsafe hooks we have?
Or should I spend less time on workarounds and more on introducing constraints to my testing system (which is also fine in a way)?

just for learning purposes[1], here's a diff of the sample project with some code that demonstrates it might be possible to use this new tool in some manner. it does require threading through static isolation info and explicitly wrapping some callers in assumeIsolated calls, so i'm not sure if that would be of use/interest (ignoring entirely the deployment target issues).

Click for diff
diff --git i/Package.swift w/Package.swift
index fd4d9e5..c60d157 100644
--- i/Package.swift
+++ w/Package.swift
@@ -6,7 +6,7 @@ let swiftSettings: [SwiftSetting] = [.swiftLanguageMode(.v6)]
 
 let package = Package(
 	name: "IsolatedAnySampleProj",
-	platforms: [.iOS(.v13), .macOS(.v12), .watchOS(.v10)],
+	platforms: [.iOS(.v26), .macOS(.v12), .watchOS(.v10)],
 	products: [
 		.library(
 			name: "IsolatedAnySampleProj",
diff --git i/Sources/IsolatedAnySampleProj/MockFunc.swift w/Sources/IsolatedAnySampleProj/MockFunc.swift
index 1a9346f..b8a1e95 100644
--- i/Sources/IsolatedAnySampleProj/MockFunc.swift
+++ w/Sources/IsolatedAnySampleProj/MockFunc.swift
@@ -12,11 +12,23 @@ public struct ResultContainer<Input, Output> {
 
 public struct CompletionContainer<Output> {
 
-	let completion: (Output) -> Void
+	let completion: @isolated(any) (Output) -> Void
 
 	@inline(__always)
-	public func callAsFunction(_ output: Output) {
-		completion(output)
+	public func callAsFunction(
+        iso: isolated (any Actor)? = #isolation,
+        _ output: Output
+    ) {
+        assert(completion.isolation === iso, "wrong isolation")
+        let initialThread = Thread.current
+        print("thr: \(initialThread)")
+        Task.immediate {
+            {
+                print("thr (imm): \(Thread.current)")
+                assert(Thread.current === initialThread)
+            }()
+            await completion(output)
+        }
 	}
 }
 
@@ -118,7 +130,8 @@ public nonisolated final class MockFunc<Input, Output>: MockFuncInvoking, @unche
 	/// 2) trigger `didCall` callback
 	/// 3) append completion to the `completions`
 	/// 4) then trigger `result` when passing `output` to the completion if `callsCompletionImmediately` is set to true.
-	public func callAndReturn(
+    public func callAndReturn(
+        iso: isolated (any Actor)? = #isolation,
 		_ input: Input,
 		completion: @escaping @isolated(any) (Output) -> Void
 	) {
diff --git i/Tests/IsolatedAnySampleProjTests/IsolatedAnySampleProjTests.swift w/Tests/IsolatedAnySampleProjTests/IsolatedAnySampleProjTests.swift
index 01e1f64..74fda8e 100644
--- i/Tests/IsolatedAnySampleProjTests/IsolatedAnySampleProjTests.swift
+++ w/Tests/IsolatedAnySampleProjTests/IsolatedAnySampleProjTests.swift
@@ -24,7 +24,9 @@ final class MockImageLoader: ImageLoader, @unchecked Sendable {
 
 	let loadUrlWithCompletionMock = MockFunc<LoadUrlItemCompletionInput, (ImageCacheItem, Image?)>()
 	func load(url: URL, item: ImageCacheItem, completion: @escaping @MainActor @Sendable (ImageCacheItem, Image?) -> Void) {
-		loadUrlWithCompletionMock.callAndReturn((url, item), completion: completion)
+        MainActor.assumeIsolated {
+            loadUrlWithCompletionMock.callAndReturn((url, item), completion: completion)
+        }
 	}
 
 	let loadUrlWithNonIsolatedCompletionMock = MockFunc<LoadUrlItemCompletionInput, (ImageCacheItem, Image?)>()

i have no special insight into this so don't know, and i don't think that in general unsafe workarounds that depend on implementation details are ever ensured to continue behaving in any particular way, so there's some risk involved. i do think the existing diagnostic will remain a warning until at least swift 7, so doing nothing for a while is also probably unlikely to cause problems (other than a warning that you maybe can't get rid of).

i haven't thought about your problem domain enough to really have much of an opinion... perhaps if you could elaborate a bit more on the constraints you want to enforce it might be clearer if there's some other way to achieve them without resorting to workarounds.


  1. mostly for myself... :sweat_smile: ↩︎

Just tried the diff you gave. Worked like a charm!


For my use cases I see a few limitations:

  • Deployment target, meaning that the framework (MockFunc) I'm working on will require the latest runtime and dev tools. Which is ok in theory, but since it's used in a large organization with lots of teams, it may be difficult to upgrade (Which is also fine. Luckily, it's an inhouse solution, not a paid product we ship to customers).
  • Mocks are being automatically generated, so the generator will have to be updated, so whenever a closure has an actor isolation, we'll have to use that actor and wrap the mock invocation into assumeIsolated. (Also doable)

At this point I do like the solution with the unsafeBitCast, since it doesn't require me to bump all the way up to iOS/macOS 26.

I also appreciate the new Task.immediate API. Probably, I used it incorrectly last time I checked and that's why it hadn't worked for me. I wish it was available in older versions of the OS.

But at this point I'm also thinking that actively not doing anything about the warning is also an action.


The domain of the tool is - make mocking dependencies unified and simple by using generics. An example of using the tool can be found here (it's a playground project with the scoped implementation, not the real thing): CorporateTestflight/VersionsBrowser/Tests/VersionsBrowserTests/VersionsListStoreTests.swift at main · SergeyPetrachkov/CorporateTestflight · GitHub.
So, any function can be tested using a MockFunc. Before Swift Concurrency iOS developers were using closures as completion handlers a lot. And in tests they used to have to use expectations to test functions with completion handlers. With MockFunc no expectations are needed, as the closure can be executed immediately and it can give a sync execution of an async logic.

When Swift Concurrency emerged, the codebase got some functions where completion handlers received MainActor isolation. It may not have been the best decision to write the code like that in the first place, but it's already there. At that point the mocks written with MockFunc received a warning like "... is losing MainActor isolation, this will be an error in Swift 6 mode".

That's when I thought of using isolated(any), but I made some mistakes in assuming that I could run the @isolated(any) (T) -> Void closures synchronously by storing them in a property of (T) -> Void type. Now I have the warning and I have more knowledge of Swift Concurrency, and when I got that warning I wasn't surprised, but I have to think of a solution to the problem.


For now, the solutions that I see are:

  • unsafeBitCast thingy
  • iOS/macOS 26 bump and Task.immediate + assumeIsolated
  • wait and eliminate the isolated completion handlers from the codebase, then remove isolated(any) from the MockFunc and make sure that the new code is written in Swift6-idiomatic way. Because if the completion handler doesn't have @MainActor or any other global actor isolation, then we don't need all this stuff. And for async-await code we have something else (that I didn't share in the sample project for the sake of keeping things scoped).

From the user perspective, it would be nice to be able to do this using dynamic casting:

let x: @isolated(any) (Output) -> Void = ...
if let y = x as? ((Output) -> Void) {
    y(output)
} else {
    fatalError("Wrong isolation")
}

Yeah, we can do that via unsafeBitCast(fn, to: ((Output) -> Void).self), but that may be a bit spooky

Given as? now checks the current executor as part of "isolated conformances", this seems extra-reasonable.

1 Like

I've been playing with this suggestion and spotted an interesting thing.
The complete runnable code snippet can be found here (same repository, added new tests file to isolate the example):

So, the issue is:

the bitcast works fine, but when I actually call the container (and thus call the closure), the arguments are corrupted for the isolated closures (nonisolated work fine).

@MainActor
@Test
func callAsFunction_shouldCallIsolatedClosure() {
	struct Arg {
		let bool: Bool
		let int: Int
	}
	let sample = Arg(bool: true, int: 42)
	let isolatedClosure: @MainActor (Arg) -> Void = { arg in
		#expect(arg.bool) // Expectation failed: (arg → Arg(bool: false, int: 10162940616)).bool → false
		#expect(arg.int == 42) // Expectation failed: (arg.int → 10162940616) == 42
	}
	let sut = CompletionContainer(isolatedClosure)

	sut.callAsFunction(sample)
}

So, I played a bit with it and thought that we don't actually have to cast the closure to (any Actor, Output) -> Void, we've got the actor anyways, then we cast the closure to the nonisolated one and assert the isolation later like this:

enum EditedCompletionContainer<Output> {
	case nonisolated((Output) -> Void)
	case isolated (any Actor, (Output) -> Void) // an actor, and a nonisolated closure

	init(
		_ fn: @escaping @isolated(any) (Output) -> Void
	) {
		// Due to runtime requirements for interacting with @isolated(any)
		guard #available(macOS 15.0, iOS 18.0, *) else {
			fatalError("Unsupported runtime!")
		}

		typealias OriginalFunction = @isolated(any) (Output) -> Void
		typealias NonIsolatedFunction = (Output) -> Void

		assert(MemoryLayout<NonIsolatedFunction>.size == MemoryLayout<OriginalFunction>.size)

		switch fn.isolation {
		case .none:
			let ret = unsafeBitCast(fn, to: NonIsolatedFunction.self) // cast the closure to the nonisolated one
			self = .nonisolated(ret) // store as is
		case .some(let actor):
			let ret = unsafeBitCast(fn, to: NonIsolatedFunction.self) // cast the closure to the nonisolated one
			self = .isolated(actor, ret) // store a reference to the actor and the casted closure
		}
	}

	func callAsFunction(_ output: Output) -> Void {
		switch self {
		case .nonisolated(let fn):
			return fn(output)
		case .isolated(let actor, let fn):
			actor.preconditionIsolated("Incorrect isolation assumption!")
			return fn(output) // call the nonisolated closure
		}
	}
}

If I do it like this, then the tests consistently pass. But the question is - did I make a correct assumption, or do I miss something fundamental?

@SergeyPetrachkov just wanted to point you to related discussion in this thread that may be of interest if you're still thinking about this: Why do I need runtime support to unsafeBitCast an @isolated<any> closure?.

1 Like