Actor isolation & delegates in extensions

Hello! I came across an interesting case lately where actor isolation is not respected or warned when conforming to a delegate from an extension on an @MainActor class. For example, the below code compiles without warnings:

protocol MyDelegate {
  func doThing()
}

@MainActor
class MyClass {
  var myCounter = 0
}

extension MyClass: MyDelegate {
  func doThing() {
    myCounter += 1
    print("Count incremented to \(myCounter)")
  }
}

var v: MyDelegate?
Task { @MainActor in
  v = MyClass()
}

DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 1) {
  v?.doThing()
}

This seems problematic as I am calling doThing() in a nonisolated context and mutating myCounter outside of the MainActor. I would expect to get a warning here telling me to mark doThing() as nonisolated or an error around my nonisolated call to doThing(). If I annotate the extension or the protocol with @MainActor I do get warnings and errors, but with the above code I don't.

Can someone help me understand what's going on here? Is this a bug, or am I just missing something? Thanks in advance!

3 Likes

MainActor only applies when using swift concurrency, not dispatch queues.

Thanks for the reply, this issue happens when using Task as well. For example:

protocol MyDelegate {
  func doThing()
}

@MainActor
class MyClass {
  var myCounter = 0
}

extension MyClass: MyDelegate {
  func doThing() {
    print("Is on main: \(Thread.isMainThread)")
    myCounter += 1
    print("Count incremented to \(myCounter)")
  }
}

var v: MyDelegate?
Task { @MainActor in
  v = MyClass()
}

Task { 
  sleep(1)
  v?.doThing()
}

I get no warnings or errors and myCounter is incremented from a non-main thread:

Is on main: false
Count incremented to 1

I would expect at least a warning here telling me to mark doThing() as nonisolated, no?

ah I see what you mean, and this is not an issue if the conformance is in the type definition scope?

That's correct. If I move the conformance up within the type definition scope, I get a "Main actor-isolated instance method 'doThing()' cannot be used to satisfy nonisolated protocol requirement" warning.

Explicitly marking the extension as @MainActor also causes the same warning to surface, but it's not clear to me why that isn't implicitly happening.

1 Like

I agree with you, I would assume the same behavior, this looks like a bug.

I filed a bug recently that seems to be similar. You can compile some unsafe code with no warnings, and Xcode will even trigger a data race warning when you run it.

I only came across this while messing with static properties on protocols, but your example shows it's a bigger problem.

Oh interesting, yes that looks quite similar. In case they have a different root cause I'll file a separate bug and tag yours. Thanks!

1 Like

Bug filed: Actor isolation not respected or warned when conforming to a delegate from an extension · Issue #61370 · apple/swift · GitHub

2 Likes