Fixing “Most important actor-isolated property can’t be referenced from a Sendable closure” in Swift – Donny Wals

Fixing “Most important actor-isolated property can’t be referenced from a Sendable closure” in Swift – Donny Wals


Revealed on: January 10, 2025

Whenever you activate strict concurrency checking otherwise you begin utilizing the Swift 6 language mode, there will likely be conditions the place you run into an error that appears slightly bit like the next:

Most important actor-isolated property can’t be referenced from a Sendable closure

What this error tells us is that we’re attempting to make use of one thing that we’re solely supposed to make use of on or from the principle actor inside a closure that is speculated to run just about wherever. In order that may very well be on the principle actor or it may very well be someplace else.

The next code is an instance of code that we might have that outcomes on this error:

@MainActor
class ErrorExample {
  var depend = 0

  func useCount() {
    runClosure {
      print(depend)
    }
  }

  func runClosure(_ closure: @Sendable () -> Void) {
    closure()
  }
}

In fact, this instance could be very contrived. You would not truly write code like this, however it’s not unlikely that you’d wish to use a essential actor remoted property in a closure that’s sendable inside of a bigger system. So, what can we do to repair this drawback?

The reply, sadly, just isn’t tremendous easy as a result of the repair will rely upon how a lot management we’ve over this sendable closure.

Fixing the error if you personal all of the code

If we fully personal this code, we might truly change the perform that takes the closure to develop into an asynchronous perform that may truly await entry to the depend property. This is what that will appear to be:

func useCount() {
  runClosure {
    await print(depend)
  }
}

func runClosure(_ closure: @Sendable @escaping () async -> Void) {
  Activity {
    await closure()
  }
}

By making the closure asynchronous, we will now await our entry to depend, which is a sound solution to work together with a essential actor remoted property from a special isolation context. Nevertheless, this won’t be the answer that you simply’re on the lookout for. You won’t need this closure to be async, for instance. In that case, when you personal the codebase, you can @MainActor annotate the closure. This is what that appears like:

@MainActor
class ErrorExample {
  var depend = 0

  func useCount() {
    runClosure {
      print(depend)
    }
  }

  func runClosure(_ closure: @Sendable @MainActor () -> Void) {
    closure()
  }
}

As a result of the closure is now each @Sendable and remoted to the principle actor, we’re free to run it and entry some other essential actor remoted state inside the closure that is handed to runClosure. At this level depend is essential actor remoted as a result of its containing sort being essential actor remoted, runClosure itself is essential actor remoted as a result of its unclosing sort being essential actor remoted, and the closure itself is now additionally essential actor remoted as a result of we added an express annotation to it.

In fact this solely works if you need this closure to run on the principle actor and when you absolutely management the code.

If you do not need the closure to run on the principle actor and also you personal the code, the earlier answer would give you the results you want.

Now let’s check out what this seems like when you do not personal the perform that takes this sendable closure. In different phrases, we’re not allowed to switch the runClosure perform, however we nonetheless must make this mission compile.

Fixing the error with out modifying the receiving perform

After we’re solely allowed to make modifications to the code that we personal, which on this case can be the useCount perform, issues get slightly bit trickier. One strategy may very well be to kick off an asynchronous process inside the closure and it will work with depend there. This is what this seems like:

func useCount() {
  runClosure {
    Activity {
      await print(depend)
    }
  }
}

Whereas this works, it does introduce concurrency right into a system the place you won’t wish to have any concurrency. On this case, we’re solely studying the depend property, so what we might truly do is seize depend within the closure’s seize record in order that we entry the captured worth reasonably than the principle actor remoted worth. Here’s what that appears like.

func useCount() {
  runClosure { [count] in
    print(depend)
  }
}

This works as a result of we’re capturing the worth of depend when the closure is created, reasonably than attempting to learn it from inside our sendable closure. For read-only entry, it is a strong answer that may work properly for you. Nevertheless, we might complicate this slightly bit and attempt to mutate depend which poses a brand new drawback since we’re solely allowed to mutate depend from inside the principle actor:

func useCount() {
  runClosure {
    // Most important actor-isolated property 'depend' can't be mutated from a Sendable closure
    depend += 1
  }
}

We’re now operating into the next error:

Most important actor-isolated property ‘depend’ can’t be mutated from a Sendable closure

I’ve devoted put up about operating work on the principle actor the place I discover a number of methods to unravel this particular error.

Out of the three options proposed in that put up, the one one that will work for us is the next:

Use MainActor.run or an unstructured process to mutate the worth from the principle actor

Since our closure is not async already, we will not use MainActor.run as a result of that is an async perform that we might should await.

Just like how you’ll use DispatchQueue.essential.async in previous code, in your new code you should utilize Activity { @MainActor in } to run work on the principle actor:

func useCount() {
  runClosure { 
    Activity { @MainActor in
      depend += 1
    }
  }
}

The truth that we’re compelled to introduce a synchronicity right here just isn’t one thing that I like rather a lot. Nevertheless, it’s an impact of utilizing actors in Swift concurrency. When you begin introducing actors into your codebase, you additionally introduce a synchronicity as a result of you’ll be able to synchronously work together with actors from a number of isolation contexts. An actor all the time must have its state and capabilities awaited if you entry it from outdoors of the actor. The identical applies if you isolate one thing to the principle actor as a result of if you isolate one thing to the principle actor it primarily turns into a part of the principle actor’s isolation context, and we’ve to asynchronously work together with essential actor remoted state from outdoors of the principle actor.

I hope this put up gave you some insights into how one can repair errors associated to capturing essential actor remoted state in a sendable closure. If you happen to’re operating into eventualities the place not one of the options proven listed below are related I would love when you might share them with me.

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 
rooshohttps://www.roosho.com
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog. 

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here


Latest Articles

author avatar
roosho Senior Engineer (Technical Services)
I am Rakib Raihan RooSho, Jack of all IT Trades. You got it right. Good for nothing. I try a lot of things and fail more than that. That's how I learn. Whenever I succeed, I note that in my cookbook. Eventually, that became my blog.