# \[Pitch\] Usability of global-actor-isolated types

**URL:** <https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799>\
**Category:** Pitches\
**Tags:** concurrency\
**Created:** [March 20, 2024, 4:28am UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799 "2024-03-20T04:28:05Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![simanerush](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/simanerush/32/30785_2.png) [@simanerush](https://forums.swift.org/u/simanerush)\
**Post date:** [March 20, 2024, 4:28am UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/1 "2024-03-20T04:28:05Z")

</div>

Hello, Swift Evolution!

Together with @hborla and @mattie, I have drafted a proposal encompassing a collection of changes to concurrency rules concerning global-actor-isolated types to improve their usability.

Global actor-isolated types have several properties that we can use to improve the existing concurrency rules.

We propose the following improvements for rules around global actor-isolated types:

- Treat `Sendable` properties of a global-actor-isolated value type as `nonisolated` within the module.

- Infer the `@Sendable` attribute for global-actor-isolated functions and closures.

- Allow globally isolated closures to capture non-`Sendable` values.

- Require the global-actor-isolated subclass of a `nonisolated`, non-`Sendable` to be non-`Sendable`.

You can view the full proposal draft here: [swift-evolution/proposals/NNNN-global-actor-isolated-types-usability.md at global-actor-isolated-types-usability · simanerush/swift-evolution · GitHub](https://github.com/simanerush/swift-evolution/blob/global-actor-isolated-types-usability/proposals/NNNN-global-actor-isolated-types-usability.md)

If you have editorial feedback, you're welcome to leave it on the swift-evolution PR here: [Add a proposal to improve usability of global-actor-isolated types. by simanerush · Pull Request #2372 · apple/swift-evolution · GitHub](https://github.com/apple/swift-evolution/pull/2372)

We're looking forward to hearing your questions, thoughts and feedback!

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 20, 2024, 6:35am UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/2 "2024-03-20T06:35:08Z")

</div>

Hello,

Thanks for those QoL improvements!

I was expecting to see something about global-actor isolated type that define **public** static properties that have a Sendable types. Those are a [real pain point](https://mastodon.social/@calicoding/111943305917036927). See [https://forums.swift.org/t/difficulty-designing-a-static-requirement-due-to-sendable-se-0412/70107/29:](https://forums.swift.org/t/difficulty-designing-a-static-requirement-due-to-sendable-se-0412/70107/29:)

> [@Difficulty designing a static requirement due to Sendable & SE-0412](https://forums.swift.org/t/difficulty-designing-a-static-requirement-due-to-sendable-se-0412/70107/29):
>
> When the enclosing type became `@MainActor`-isolated, so did all of these constants. I wonder if the compiler should issue a warning if you've written a `static let` of `Sendable` type in an actor-isolated context with a fix-it to insert `nonisolated`. You can already access the variable as if it were `nonisolated` from within the module, but you may very well want that to be the case outside the module as well. Perhaps this is also something that the Clang importer can do by default, which would fix these cases with `NSNotification.Name`.

ADDENDUM:

I can't put my finger on it now (some Mastodon thread), but it looks like there is two distinct problems with static properties of Sendable type:

```swift
@MainActor
public class MyType {
    // 1
    public static let constant = "foo"

    // 2
    public static var notConstant: String { "bar" }
}

```

I was referring to 1, the `constant`, in the beginning of this post. It should be nonisolated:

```swift
@MainActor
public class MyType {
    // The CORRECT (i.e. not painful for users) declaration
    public nonisolated static let constant = "foo"
}

```

On this other side, the `var notConstant`, as declared, is isolated, and should be, since it is declared as mutable.

**But some Objective-C frameworks define constants that are imported as `var`.** That's where I can't find the link to a recent example.

I forgot my Objc, so please excuse this gross attempt at reproducing the problem:

```objc
// That's a static property exported as `var` in Swift, right?
NS_SWIFT_UI_ACTOR
@interface MyType
@property (class, readonly) NSString *constantButExportedAsVar;
@end

@implementation MyType
@dynamic constantButExportedAsVar;
+ (NSString *)constantButExportedAsVar {
    // This is constant, but nobody knows 😖
    // Swift compiler can't suggest to make it nonisolated.
    return @"bar";
}
@end

```

I would expect some audit of Apple frameworks in this regard.

---

<div class="post-metadata">

**Author:** ![tcldr](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tcldr/32/24405_2.png) [@tcldr](https://forums.swift.org/u/tcldr)\
**Post date:** [March 20, 2024, 1:25pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/3 "2024-03-20T13:25:17Z")

</div>

Hi all, happy to hear about these improvements to strict concurrency. One improvement that I don't see mentioned is around global actor isolated existentials.

For example the following currently generates a diagnostic:

```swift
@MainActor final class C {}
@MainActor protocol P: AnyObject {}

struct S {
  // Happy days
  static let defaultValueA: C? = nil
  // Static property 'defaultValueB' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this is an error in Swift 6
  static let defaultValueB: P? = nil
}

```

Is this something that could be addressed a part of this proposal?

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [March 20, 2024, 3:37pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/4 "2024-03-20T15:37:51Z")

</div>

> [@gwendal.roue](#):
>
> I was expecting to see something about global-actor isolated type that define **public** static properties that have a Sendable types.

I thought about this more and it's not just about whether or not the property type is a `let` and `Sendable`. The initializer for the static variable must also not be global-actor-isolated. The isolated default value proposal makes decisions about the isolation of the member-wise initializer based on the isolation of stored property initial values because the member-wise initializer is always `internal` and it's not an API promise, but I'm strongly against applying such inference to public API when it's not explicit.

> [@tcldr](#):
>
> For example the following currently generates a diagnostic:
> 
> ```swift
> @MainActor final class C {}
> @MainActor protocol P: AnyObject {}
> 
> struct S {
> // Happy days
> static let defaultValueA: C? = nil
> // Static property 'defaultValueB' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this is an error in Swift 6
> static let defaultValueB: P? = nil
> }
> 
> ```
> 
> Is this something that could be addressed a part of this proposal?

I think there are two different, subtle questions here.

1. Can global actor isolation on a protocol imply that the protocol refines `Sendable`?

No, because global actor isolation on a protocol does not require the conforming type to be isolated to that global actor. Global actor isolation on a protocol does two things: 1) it applies the global actor to all protocol requirements, and 2) it's an inference source for conforming types if the conformance is stated at the primary declaration. If you add a conformance to a global actor isolated protocol via an extension outside the defining module, or even inside the defining module in another source file, the global actor attribute cannot be inferred on the entire type, so all mutable state of that type may be unprotected. For this reason, it's completely possible for global actor isolated protocols to be conformed to by non-`Sendable` types. The way to require `Sendable` is to explicitly write it in the protocol:

```swift
@MainActor protocol P: AnyObject, Sendable {}

```

1. Can the unsafe static variable warning be suppressed if it's a `let` constant and the underlying initial value actually is `Sendable`?

If you wrote this:

```swift
@MainActor final class C: P {}

@MainActor protocol P: AnyObject {}

struct S {
  // Static property 'defaultValueB' is not concurrency-safe because it is not either conforming to 'Sendable' or isolated to a global actor; this is an error in the Swift 6 language mode
  static let defaultValueB: P? = C()
}

```

Then the underlying value for `defaultB` is `Sendable`, meaning there's no potential for unsafe concurrent access here and the warning could be suppressed. However, this isn't about global actor isolated types and I don't think that discussion belongs in this proposal. I think that can just be a small revision to SE-0412.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 20, 2024, 3:51pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/5 "2024-03-20T15:51:45Z")

</div>

> [@hborla](#):
>
> I thought about this more and it's not just about whether or not the property type is a `let` and `Sendable`. The initializer for the static variable must also not be global-actor-isolated. The isolated default value proposal makes decisions about the isolation of the member-wise initializer based on the isolation of stored property initial values because the member-wise initializer is always `internal` and it's not an API promise, but I'm strongly against applying such inference to public API when it's not explicit.

Thank you. In your initial message you were talking about a warning, so that the developers would have to add an explicit `nonisolated` in front of their `public static let`. This sounded sensible. There's no inference in this case - everything is explicit.

My concern is that all bug reports and feedbacks that devs can submit to Apple frameworks will be left open in limbo, or closed as "works as expected" because the level of maturity required to consider "1. the type is isolated but this nested constant _does not need to be_ and 2. this is just bothersome for users _who are not me_, 3. let's apply this trivial QoL enhancement _right away_" is high. A little kick (a warning) from the compiler would help framework devs to make the correct choice. There is an opportunity here.

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [March 20, 2024, 3:56pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/6 "2024-03-20T15:56:58Z")

</div>

> [@gwendal.roue](#):
>
> In your initial message you were talking about a warning, so that the developers would have to add an explicit `nonisolated` in front of their `public static let`. This sounded sensible. There's no inference in this case - everything is explicit.

Yes, I still think a warning is a good idea! I don't think such a warning needs an evolution proposal, but it can apply if:

1. The static variable is a `let`
2. The type of the static variable is `Sendable`
3. The initial value of the static variable does not require isolation
4. The static variable is inferred to have global actor isolation

The warning could be silenced by writing an explicit isolation on the variable, either `nonisolated` if that's what the programmer meant, or a global actor annotation if the programmer wants to leave the door open to later change the static variable to a computed property or change the initial value in a way that might require isolation.

Feel free to file a GitHub issue if you'd like to track that change.

---

<div class="post-metadata">

**Author:** ![tcldr](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/tcldr/32/24405_2.png) [@tcldr](https://forums.swift.org/u/tcldr)\
**Post date:** [March 20, 2024, 4:23pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/7 "2024-03-20T16:23:17Z")

</div>

> [@hborla](#):
>
> No, because global actor isolation on a protocol does not require the conforming type to be isolated to that global actor. Global actor isolation on a protocol does two things: 1) it applies the global actor to all protocol requirements, and 2) it's an inference source for conforming types if the conformance is stated at the primary declaration.

OK, yes. I was under the assumption that actor isolation on a protocol implied that the conforming type would be isolated to the global actor, too. But in fact that's only the case 'if the conformance is stated at the primary declaration'. Good to know, thanks!

---

<div class="post-metadata">

**Author:** ![vns](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/vns/32/37396_2.png) [@vns](https://forums.swift.org/u/vns)\
**Post date:** [March 20, 2024, 4:29pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/8 "2024-03-20T16:29:49Z")

</div>

I've revisited proposal on global actors, under SE-316 statements protocol implementations within the same module inherit protocol isolation as whole, but outside of the module does not (_wonder why distinction was made?_). And clearly there is no way to know if that global variable with protocol type has implementation from the same module or not, so its assumed it does not inherit such isolation.

---

<div class="post-metadata">

**Author:** ![gwendal.roue](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/gwendal.roue/32/13992_2.png) [@gwendal.roue](https://forums.swift.org/u/gwendal.roue)\
**Post date:** [March 20, 2024, 4:32pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/9 "2024-03-20T16:32:12Z")

</div>

> [@hborla](#):
>
> Feel free to file a GitHub issue if you'd like to track that change.

🤩 🎁 [Global-actor isolated types: emit a warning for public sendable static properties that miss an explicit isolation annotation. · Issue #72456 · apple/swift · GitHub](https://github.com/apple/swift/issues/72456)

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [March 20, 2024, 11:29pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/10 "2024-03-20T23:29:48Z")

</div>

> This means that for such global-actor-isolated closures and functions, the `@Sendable` attribute is implicit.

I think the same logic applies to the closures isolated on the actor instance. So all isolated closures and functions can be implicitly @Sendable.

> Note that under region isolation in SE-0414, capturing a non-`Sendable` value in an actor-isolated closure will transfer the region into the actor, so it is impossible to have concurrent access on non-`Sendable` captures even if the isolated closure is formed outside the actor.

Looks like it is not implemented yet. Using `swift-DEVELOPMENT-SNAPSHOT-2024-03-13-a.xctoolchain` and `-enable-experimental-feature RegionBasedIsolation` I’m still getting an error. Anyway, I think that’s a very important improvement. Looking forward. Thank you for fixing this!

> Subclasses may add global actor isolation when inheriting from a nonisolated, non-`Sendable` superclass. In this case, an implicit conformance to `Sendable` will not be added, and explicitly specifying a Sendable conformance is an error:

That’s unnecessarily restrictive. I think we should treat superclass subobject as an non-Sendable object isolated to a region connected to the actor of the subclass. Upcasting should preserve that.

When erasing to existentials of protocols that don’t refine Sendable, is the resulting value considered to be non-Sendable and tracked by the region-based isolation?

But adding isolation in the subclass should be banned if class has required initializers:

```swift
class Base {
    required init() {}
}

@MainActor
class Derived: Base {
    required override init() {}
}

actor MyActor {
    var obj: Base
    init<C: Base>(_: C.Type) {
         // C.init() returns object isolated to self
        self.obj = C()
    }
}

_ = MyActor(Base.self) // ok
_ = MyActor(Derived.self) // unsound

```

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [March 20, 2024, 11:56pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/11 "2024-03-20T23:56:22Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> I think the same logic applies to the closures isolated on the actor instance. So all isolated closures and functions can be implicitly @Sendable.

Actor-instance-isolated closures can only be `@Sendable` if the closure type preserves the fact that it was isolated. The only way to represent this in the type system is using `@isolated(any)`, and `@isolated(any)` function types are already specified to be `@Sendable` in the corresponding pitch.

> [@Nickolas\_Pohilets](#):
>
> Looks like it is not implemented yet. Using `swift-DEVELOPMENT-SNAPSHOT-2024-03-13-a.xctoolchain` and `-enable-experimental-feature RegionBasedIsolation` I’m still getting an error. Anyway, I think that’s a very important improvement. Looking forward. Thank you for fixing this!

That's right; this proposal lifts the restriction, and this proposal isn't implemented yet. This note is just to clarify that SE-0414 makes this rule safe.

> [@Nickolas\_Pohilets](#):
>
> That’s unnecessarily restrictive. I think we should treat superclass subobject as an non-Sendable object isolated to a region connected to the actor of the subclass. Upcasting should preserve that.

I don't understand how allowing a `Sendable` conformance on the subclass is safe because superclass state is unprotected by the global actor, and there are still ways to access that unprotected state through the subclass. Are you imaging that inherited state/methods are somehow retroactively treated as isolated by the global actor? The proposal outlines some reasons why I don't believe that's possible.

EDIT: I've been staring at this for a while and I can't reconcile how a "non-Sendable subobject" of a sendable instance would work, because region isolation is cut off as soon as you reach a Sendable part of an object graph. Would you mind elaborating on how that would work? For example, how would this formulation prevent calling inherited `nonisolated async` methods from the superclass, which can access the unprotected state on `self` while that unprotected state presumably can be concurrently accessed from the global actor?

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [March 21, 2024, 10:45am UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/12 "2024-03-21T10:45:03Z")

</div>

Region isolation does not apply as long as we operate on a derived type, but should come back into play as soon as reference is upcasted. And upcasted reference should be in the region connected to the global actor of the subclass. And this should happen both when upcasting explicitly and when accessing members of the superclass.

Now, `nonisolated async` methods actually are isolated to the current task. So if the value is isolated to an actor, such methods cannot be called on that value, because it is not legal to merge actor-isolated region with task-isolated one.

```swift
// generic over isolation region
class Base {
    var k: Int = 0

    // Generic isolation
    func foo() { k += 1 }

    // Specific isolation - isolated to the current task
    // Can be called only if self is isolated to the current task too
    func bar() async {}

    // Generic isolation
    // Can be called only if self is isolated to the `owner`.
    func bar(owner: isolated (any Actor)?) {}
}

@MainActor
class Derived: Base/*<@MainActor>*/ {
    var mutable = 0

    // Isolated to @MainActor
    override func foo() {
        super.foo()
        mutable += 1 // ok
    }

    // Isolation does not exist.
    // self is isolated to invalid region.
    // This method is statically known to be unreachable.
    // As if it has an argument of type Never.
    override func bar() async {}

    // Technically this should be ok, but can be tricky to implement.
    // It is isolated both to @MainActor and to owner.
    // We statically know that owner is MainActor.shared.
    override func bar(owner: isolated (any Actor)?) {}
}

let d = Derived()
// []
let b: Base = d
// [{b, @MainActor}]
b.foo() // ok if current context is isolated to @MainActor
await b.bar() // error: cannot transfer MainActor-isolated value to the current task
await b.bar(owner: nil) // error: cannot transfer MainActor-isolated value to the current task
await b.bar(owner: MainActor.shared) // ok

```

If we use generic types, instead of isolations then example above is equivalent to the following:

```swift
class Base<Isolation> {
    func foo() {}
    func bar() where Isolation = Task {}
    func bar(owner: Isolation.Type) {}
}

class Derived: Base<MainActor> {}

let d = Derived()
d.bar() // error: instance method 'bar()' requires the types 'MainActor' and 'Task' be equivalent

```

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [March 21, 2024, 3:28pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/13 "2024-03-21T15:28:08Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> Region isolation does not apply as long as we operate on a derived type, but should come back into play as soon as reference is upcasted. And upcasted reference should be in the region connected to the global actor of the subclass. And this should happen both when upcasting explicitly and when accessing members of the superclass.

There are ways to access superclass members without upcasting, for example:

```swift
protocol P {
  func requirement()
}

class Super: P {
  var mutable = 0
  func requirement() {
    mutable += 1
  }
}

@MainActor class Sub: Super /*, Sendable */ {
}

func generic<T: P>(_ t: T) async {
  t.requirement()
}

@MainActor func test(s: Sub) async {
  Task.detached {
    await generic(s)
  }

  s.mutable += 2
}

```

We can model this by preventing the subclass type from being `Sendable` instead of inventing bespoke rules to prevent specific things that are unsafe like upcasting and inherited conformances. It's a much easier rule to understand and catching the unsafety falls out of existing rules.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [March 21, 2024, 3:38pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/14 "2024-03-21T15:38:56Z")

</div>

`generic()` is async and nonisolated, meaning it is isolated to the current task. `T` is not known to be `Sendable`, to `t` is a value tracked by region-based isolation.

It is valid to capture `s: Sub` in the closure passed to `Task.detached()` - this preserves information that `s` is isolated to the `@MainActor`.

But passing `s` to `generic()` is not, because `generic()` expects argument isolated to the current task, while `s` is isolated to the `@MainActor`.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [March 21, 2024, 4:05pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/15 "2024-03-21T16:05:15Z")

</div>

Trying similar example but without subclassing, I see that it fails, but IMO for a wrong reason:

```swift
protocol P {
  func requirement()
}

@MainActor
class K: P {
  var mutable = 0

  // error: main actor-isolated instance method 'requirement()' cannot be used to satisfy nonisolated protocol requirement
  func requirement() {
    mutable += 1
  }
}

func generic<T: P>(_ t: T) async {
  t.requirement()
}

@MainActor func test(s: K) async {
  Task.detached {
    await generic(s)
  }

  s.mutable += 2
}

```

I think conformance should be allowed, but passing `s` to `generic()` - not.

Looks like currently we interpret lack of isolation for `P.requirement()` as the fact that it can be accessed from any isolation. But then conformance `Super: P` also should be incorrect, because `Super.requirement()` is also not accessible from arbitrary isolation context, but only from the isolation context where instance of `Super` is isolated to, according to region analysis.

That intepretation of `P.requirement()` would be valid if `P` refines `Sendable`. Then value conforming to `P` does not depend on any regions, and lack of isolation means that it can be accessed from arbitrary isolation context.

But if `P` does not refine `Sendable`, then we should assume that it is implemented by a non-sendable type, tracked by region-based isolation. And tracking applies even when value is viewed through the protocol - as existential or as a value of generic type.

---

<div class="post-metadata">

**Author:** ![John\_McCall](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/john_mccall/32/1351_2.png) [@John\_McCall](https://forums.swift.org/u/John_McCall)\
**Post date:** [March 21, 2024, 4:33pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/16 "2024-03-21T16:33:29Z")

</div>

I think Holly is acknowledging that the rule you're identifying could probably be made to work, but she's also saying that it would be brittle, require a lot of extra logic, and largely defeat the purpose of having a superclass. I think she's right about all that; the idea that you can't freely convert a class reference to its superclass type is very unnatural in object-oriented programming. And Holly's suggested approach here doesn't preclude allowing it in the future if we feel it's important to take on. Do you have a concrete example of code you think would need to do this?

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [March 21, 2024, 5:33pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/17 "2024-03-21T17:33:09Z")

</div>

I don't have any good examples where one would need to share GAI-subclass of non-sendable superclass with another isolation domain. I don't think it will be a high-demand feature.

I'm more concerned about reasons that prevent us from doing so - I suspect they can be symptoms of unsoundness in isolation checking. So allowing sending GAI-subclasses is a useful exercise to identify and fix them.

Allowing GAIT's to be `Sendable` in some cases, but not in other, IMO, is exactly "extra logic" that makes type checker less predictable and harder to understand. Imagine as a developer, you have a GAIT class, which has some logic which you want to reuse in other isolation domains. So you extract it into non-sendable base class. But now suddenly your class becomes non-Sendable. Few people would be affected by this, but those who will, gonna be very surprised.

Actually, I think being able to meaningfully override methods from the superclass will have much bigger impact. And this is an example of a side effect improvement that we get when trying to trace what prevents the sendability.

Or another example, what I wrote above about required initialisers. Do you agree that allowing GAI-subclasses with required initialisers would be unsound? Identifying this, is another side effect, that follows from the suggested rules.

So far, I was not able to understand your point about not being able to freely convert a class reference to its superclass type. If we don't retain isolation during upcasting, I'm not even sure what are the alternatives. Will the value live in the disconnected region? Or it won't be tracked by the region-based isolation at all? Or is upcasting banned? How do regions look like in the example below?

```swift
class Base {}
@MainActor class Derived: Base {}
let d = Derived()
let b: Base = d

```

---

<div class="post-metadata">

**Author:** ![hborla](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/hborla/32/27563_2.png) [@hborla](https://forums.swift.org/u/hborla)\
**Post date:** [March 21, 2024, 5:49pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/18 "2024-03-21T17:49:38Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> But passing `s` to `generic()` is not, because `generic()` expects argument isolated to the current task, while `s` is isolated to the `@MainActor`.

That's now how `Sendable` checking nor region isolation work. If `s` is `Sendable` then it is not included in region analysis - it doesn't need to be _because_ it's `Sendable`. The value `s` itself is not isolated to the main actor - what global actor isolation means on a type is all state and methods in that type are isolated to the global actor. This is the property that typically enables the implicit `Sendable` conformance - an instance of the type itself can be referenced by multiple isolation domains concurrently, but accessing its state needs to go through the actor's serialization mechanism, so the shared mutable state is protected by the global actor. Under region isolation, an instance of a main actor isolated class is not itself in the main actor region, but all of the non-`Sendable` state that you can get out of that instance are in the main actor region because they necessarily are returned from isolated methods.

`Sendable` checking also looks through to the underlying argument value, because even if the parameter type is not required to be `Sendable`, there's no potential for data races if the argument value passed is `Sendable`. This eliminates a very common source of false-positive data-race errors under the Swift 6 language mode.

The example I provided does not need `generic` to be `async` in order to demonstrate the problem:

```swift
protocol P {
  func requirement()
}

class Super: P {
  var mutable = 0
  func requirement() {
    mutable += 1
  }
}

@MainActor class Sub: Super /*, Sendable */ {
}

func generic<T: P>(_ t: T) {
  t.requirement()
}

@MainActor func test(s: Sub) async {
  Task.detached {
    generic(s)
  }

  s.mutable += 2
}

```

And changing the rules so that you cannot pass instances of global actor isolated types to generic functions that don't specify isolation in favor of enabling more subclassing patterns sounds like the wrong tradeoff to me.

> [@Nickolas\_Pohilets](#):
>
> Looks like currently we interpret lack of isolation for `P.requirement()` as the fact that it can be accessed from any isolation. But then conformance `Super: P` also should be incorrect, because `Super.requirement()` is also not accessible from arbitrary isolation context, but only from the isolation context where instance of `Super` is isolated to, according to region analysis.

The implications of "callable from an arbitrary isolation domain" are directly impacted by whether or not the value you're talking about is `Sendable`. Unspecified / nonisolated synchronous methods do indeed mean that the function is callable from any isolation domain, but if the method is on a non-`Sendable` type, the guarantee that you get is the method can never be called concurrently. Only one isolation domain ever has access to the value at a given point in the program, and that isolation domain is allowed to call the nonisolated method regardless of what that isolation domain is.

> [@Nickolas\_Pohilets](#):
>
> I'm more concerned about reasons that prevent us from doing so - I suspect they can be symptoms of unsoundness in isolation checking.

I have yet to see an example of unsoundness in isolation checking that are a result of the rules (not just compiler bugs), but if you have any examples of unsoundness, please do surface them. The impression that I've gotten from this discussion is that you're trying to enable more code to become valid under strict concurrency checking, not that you're concerned about holes in the model.

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [March 21, 2024, 9:27pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/19 "2024-03-21T21:27:16Z")

</div>

Thank you for the detailed answer. It did help me to understand how typechecking works currently.

> [@hborla](#):
>
> Under region isolation, an instance of a main actor isolated class is not itself in the main actor region, but all of the non-`Sendable` state that you can get out of that instance are in the main actor region

Doesn't this apply to the superclass subobject?.

> [@hborla](#):
>
> This eliminates a very common source of false-positive data-race errors under the Swift 6 language mode.

I'm not familiar with the issue. Could you give an example?

> [@hborla](#):
>
> And changing the rules so that you cannot pass instances of global actor isolated types to generic functions that don't specify isolation in favor of enabling more subclassing patterns sounds like the wrong tradeoff to me.

You are still able to pass GAITs to generic functions under the rules that I've suggested:

```swift
@MainActor func test(s: Sub) async {
  Task.detached {
    // ok
    // equivalent to:
    // let s1: some P = s // [{s1, @MainActor}]
    // generic(s1)
    // s1 cannot be passed to the synchronous generic(),
    // because it requires it's argument to be isolated to the current context.
    generic(s)

    // await-ing to hop to the MainActor should be able to fix this
    // but none of the 
    await generic(s)
  }

  // ok
  // equivalent to:
  // let s2: some P = s // [{s2, @MainActor}]
  // generic(s2)
  // s2 can be passed to the synchronous generic() in the @MainActor context
  generic(s) 
  s.mutable += 2
}

```

I'm a bit confused about what consitutes a tradeoff here. Do you see a decrease in expressivity or you mean only increased implementation costs?

> [@hborla](#):
>
> The impression that I've gotten from this discussion is that you're trying to enable more code to become valid under strict concurrency checking, not that you're concerned about holes in the model.

Well, both. My professional intuition sounds an alarm, and I'm trying to work out what exactly it is concerned about. I guess it could be both false positives (lack of expressiveness) and false negatives (unsoundness). But so far, indeed, discussion have surfaced no cases of unsoundness, only limitations in expressiveness.

Let's put my suggestion aside for a while, and focus on the proposal.

1. Under the current proposal, how do regions look like for the following snippet?

```swift
class Base {}
@MainActor class Derived: Base {}
let d = Derived()
let b: Base = d

```

1. Under the current proposal, can GAI-subclasses add their isolation when overriding methods from the superclass?

```swift
class Base {
    var counter: Int = 0
    func increment() {
        counter += 1
    }
}

@MainActor
class Derived: Base {
    var isCounting: Bool = false
    func increment() {
        isCounting = true
        super.increment()
        isCounting = false
    }
}

```

1. Under the current proposal, can GAI-subclasses exist in the present of the required initialisers? Does overridden initialiser need to be `nonisolated` or can it be isolated?

```swift
class Base {
    required init() {}
    func foo() {}
}

@MainActor
class Derived: Base {
     override required init() {
        super.init()
     }
}

actor Owner {
    let object: Base

    init(factory: Base.Type) {
        self.object = factory.init()
    }
}

```

---

<div class="post-metadata">

**Author:** ![Nickolas\_Pohilets](https://sea2.discourse-cdn.com/swift/user_avatar/forums.swift.org/nickolas_pohilets/32/10659_2.png) [@Nickolas\_Pohilets](https://forums.swift.org/u/Nickolas_Pohilets)\
**Post date:** [March 25, 2024, 1:57pm UTC](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799/20 "2024-03-25T13:57:43Z")

</div>

> [@Nickolas\_Pohilets](#):
>
> Under the current proposal, can GAI-subclasses add their isolation when overriding methods from the superclass?

To be able to safely override nonisolated method is non-Sendable superclass with isolated method in derived class, compiler would need to be able to prove absence of conformance to Sendable. For `@unchecked` conformance, this is not possible, because `@unchecked` conformance can be retroactive.

`@unchecked Sendable` conformance is be used to circumvent isolation checking even without subclassing - this might an argument to disregard `@unchecked` conformance. But even with `@unchecked` conformance out, it is still quite tricky to prove even for conditional checked conformance. If subclass does not fully specify all generic params of the base class, then this is not provable in general case.

I understand now that lack of `Sendable` conformance means that current code is not allowed to share the value with other isolation domain, but does not mean that some other code won't share it.

But this might be still applicable to the protocol conformance. For protocols we know if they refine `Sendable` or not, and this cannot be changed retroactively. So potentially we can allow GAIT's to satisfy nonisolated requirements with isolated implementations. But, of course, that's out of scope of the pitch being discussed.

[Next page](https://forums.swift.org/t/pitch-usability-of-global-actor-isolated-types/70799.md?page=2)
