Learning About Swift Concurrency (from Matt Massicotte’s Blog) with a Zettelkasten

I just wanted to do a quick recording of one of Matt’s Concurrency posts and how I use my Zettelkasten to process the content and integrate it with existing notes. A live demo. Short and sweet.

Oh boy did the end throw a curveball.

So the video is 80min long, not 20.

The surprise lies in me realizing that some things are complicated in a different way, and I only realized that there was the equivalent of an API or contract mismatch between two notes that I couldn’t quite resolve.

Spoiler:

This is what I ended up saving in my notes to put an end to my suffering.

Hypothesis: You could say that a Sendable type’s members are by default nonisolated: being sendable across isolation domains means not being isolated to a domain.

I’m not sure whether that’s completely accurate or a good-enough rule of thumb.

But the actual bummer comes in the end, captured in a reflective note that addresses my current confusion:

# 202511110820 How does actor isolation and Sendability play together
#sendable #actor-isolation

I don't quite grasp how the following two forces in Swift Concurrency play together:

1. Conforming to `Sendable` tells the compiler that the type is essentially not isolated. It can pass between/across isolation domains. [[202511110711 Sendable essentially marks type members as nonisolated]]

2. Swift `actor`s isolate their members by default. Making a reference type `Sendable` requires either:    
    - Global actor isolation. (Global actor isolated types are `Sendable` automatically.[[202511110735]]) `@MainActor class Foo`
    - Defining a custom `actor` to protect the internal mutable state. (Sendable protocol conformance sufficient condition for Swift actor usage.[[202509160902]]) `actor Foo`

Starting with global actor isolation grants `Sendable` conformance automatically (in most cases) because the global actor takes care of managing access to the members (properties).

Starting with a `Sendable` protocol marks the members of the protocol as being able to cross isolation domain boundaries. That in turn means the members need not be isolated.

A custom actor protects its members by isolating them. A custom actor is also a valid implementation for a `Sendable` protocol. Because the actor itself is sendable by virtue of its protection guarantees. **What does that make the protocol's members do?** Are they nonisolated still?# 202511110820 How does actor isolation and Sendability play together
#sendable #actor-isolation

I don't quite grasp how the following two forces in Swift Concurrency
play together:

1. Conforming to `Sendable` tells the compiler that the type is
   essentially not isolated. It can pass between/across isolation
   domains. [[202511110711 Sendable essentially marks type members as
   nonisolated]]

2. Swift `actor`s isolate their members by default. Making a reference 
   type `Sendable` requires either:    
    - Global actor isolation. (Global actor isolated types are
      `Sendable` automatically.[[202511110735]]) `@MainActor class
      Foo`
    - Defining a custom `actor` to protect the internal mutable state.
      (Sendable protocol conformance sufficient condition for Swift
      actor usage.[[202509160902]]) `actor Foo`

Starting with global actor isolation grants `Sendable` conformance
automatically (in most cases) because the global actor takes care of
managing access to the members (properties).

Starting with a `Sendable` protocol marks the members of the protocol
as being able to cross isolation domain boundaries. That in turn means
the members need not be isolated.

A custom actor protects its members by isolating them. A custom actor
is also a valid implementation for a `Sendable` protocol. Because the
actor itself is sendable by virtue of its protection guarantees.
**What does that make the protocol's members do?** Are they
nonisolated still?

That is by no stretch of the imagination a publishable piece of information. It’s a lab notebook entry at best: finding out that two things don’t yet gel together, and that my understanding of the topic needs to be expanded.

Sometimes, that’s as far as I get, especially with parenting duty in the late morning hours. There are arbitrary stops in my work that didn’t exist before, and this note is a breadcrumb for future-me to pick up.

To reconcile these two ‘forces’ I wondered about, one Swift file later, the compiler tells me what’s up:

protocol Foo: Sendable {
    var sendableProp: Int { get set }

    // Because it's in a Sendable type, it's not isolated: true?
    func nonisolatedFunc()  
}

Try with this conformance, and you get immediate feedback:

actor Bar: Foo { 
//    |    |- error: conformance of 'Bar' to protocol 'Foo' crosses into actor-isolated code and can cause data races [#]8;;https://docs.swift.org/compiler/documentation/diagnostics/conformance-isolation\ConformanceIsolation]8;;\]
//    |    `- note: turn data races into runtime errors with '@preconcurrency'
//    `- note: mark all declarations used in the conformance 'nonisolated'
    var sendableProp: Int = 0
//      `- note: actor-isolated property 'sendableProp' cannot satisfy nonisolated requirement    
    
    func nonisolatedFunc() {
//       `- note: actor-isolated instance method 'nonisolatedFunc()' cannot satisfy nonisolated requirement
    }
}

So yes, non-isolation and actor isolation don’t gel, period! My intuition was right, and this simple test shows it. To make this compile the function needs to be explicitly nonisolated, and sendableProp needs to be nonisolated(unsafe) to make protocol conformance even possible, indicating manual management of concurrency safety. (Which, here, is a lie!)

actor Bar: Foo {
    nonisolated(unsafe) var sendableProp: Int = 0
    nonisolated func nonisolatedFunc() { }
}

See, the proof is in the pudding. So what about main actor isolation, which implies sendability? That also doesn’t compile:

@MainActor
class Bar: Foo {
//         |- warning: conformance of 'Bar' to protocol 'Foo' crosses into main actor-isolated code and can cause data races; this is an error in the Swift 6 language mode [#]8;;https://docs.swift.org/compiler/documentation/diagnostics/conformance-isolation\ConformanceIsolation]8;;\]
//         |- note: isolate this conformance to the main actor with '@MainActor'
//         `- note: turn data races into runtime errors with '@preconcurrency'

    var sendableProp: Int = 0
//      `- note: main actor-isolated property 'sendableProp' cannot satisfy nonisolated requirement

    func nonisolatedFunc() { }
//       `- note: main actor-isolated instance method 'nonisolatedFunc()' cannot satisfy nonisolated requirement

}

Make sure to read the documentation that the compiler helpfully links to: Protocol conformances crossing into actor-isolated code (ConformanceIsolation)