[Prospective Vision] Improving the approachability of data-race safety

Here are some second thoughts about the prospective vision.

First, I have high hopes in what the vision document calls "Single-threaded code", even if I think it should quickly be renamed "single-isolation-domain code".

Next, I'm not sure that at this stage, a bottom-up approach will give good results. Building a sound system and hope for the best when it is put in the hands of the developers has been useful when concurrency was introduced, because soundness was paramount.

Now a top-down approach can be useful. We could even "design for the worse", i.e. design with the most complex programs in mind, and try hard to be useful to them.

One of those very complex programs are server apps. They are intensely parallel. Still, could we help server frameworks helping their users don't bother much with concurrency? For example, if a framework could specify that app startup is performed on the main thread, and that each request is performed on a specific isolation, could it help simplifying user code? Those are two facets of the framework where the user can be expected to write "single-isolation-domain code", even though the app as a whole runs in multiple isolation domains.

This has me thinking that libraries should be able to define "concurrency contexts". The stdlib would define the context where all user code runs on the main actor. Libraries can define their own contexts, which tell the compiler what can be assumed in the code that opts in for this context.

User code would look like:

import MyServerFramework

context Startup // defined by MyServerFramework

// <- Here code is assumed to run on the `Startup` global actor

context RequestHandling // defined by MyServerFramework

// <- Here all code is assumed to run on the
// isolation of the caller, as if all methods had an
// `isolation: isolated (any Actor)? = #isolation`
// argument).

You can see above that the concurrency context is set for all code that follows a context directive. Contextualized code is not indented. Being able to use several contexts is a single file helps people write self-contained sample code (e.g. frameworks showcases, or user code reported by people looking for support).

You can see above that [Pitch] Inherit isolation by default for async functions is addressed with the RequestHandling context.

The stdlib defines the MainActor context:

context MainActor

// <- Here all code is assumed to run on the main actor

Libs can define a default context. For example, SwiftUI would define MainActor as its default context:

import Lib
context import SwiftUI // use the default context of SwiftUI
import OtherLib

// <- Here all code is assumed to run on the main actor

Ambiguity is resolved by specifying the module name:

import Lib1
import Lib2

// context Startup // error: ambiguous
context Lib2.Startup

Package targets can choose a default context:

.target(name: "MyTarget", context: "MainActor")

It looks these concurrency contexts would give a lot of space for further design.

5 Likes