Let's put aside the challenges of both errno
specifically and TLS more generally. For ordinary C global variables, I think the only reasonable option is probably to treat them as if they had unsafely opted out of isolation checking, which would allow direct uses from any context under the assumption that the programmer knows how to do that safely. That would be embracing a source of unsafety, but I think that would be acceptable for a couple reasons; firstly, APIs centered around directly accessing a mutable global variable are pretty uncommon; and secondly, such APIs are really just inherently unsafe in ways that clients have to know to be careful with, just like they have to know to not use strtok
in real code. Programmers will have tools for making those APIs safe again in Swift, either by declaring the variable to be isolated to some global actor in C (e.g. with __attribute__((swift_attr("@MainActor")))
) or just by wrapping it in a safer Swift API that acquires the right locks or declares the right isolation or whatever.
+1
It feels like there is a weak point here, in the interop to C. From "safeness" perspective it'd be good to require some kind of explicit consent to use unsafely non-isolated entities from a context. Especially under "completely" strict mode.
For a particular case of accessing C global variables it might seem like an overkill, as it's really uncommon. But calls to C functions are pretty common and I believe we can't reason about if a particular C function is really "safe" to be called from an arbitrary context.
PS: Also it'd be cool to see isolation-ness in generated interfaces.
and secondly, such APIs are really just inherently unsafe in ways that clients have to know to be careful with
Along the same lines, there are many C APIs that operate on global mutable state behind function calls and they have exactly the same sort of requirements on the programmer to know how to safely make those calls (e.g. only from a single thread). You can't really avoid this unless you restrict all unknown C function calls.
I'd like to talk about these
Swift is not likely to get direct support for thread-local variables, but they are expressible in C and will get imported into Swift. errno
is the most prevalent, but won't be the only one.
I think we can define a model here. A thread-local variable (even a mutable one) can be accessed freely within synchronous code so long as it has Sendable
type. Thread-local variables with non-Sendable
types can't be permitted because that would allow non-Sendable
values to sneak across task and actor boundaries.
We would also need to prevent a thread-local variable from being passed inout
to an asynchronous function, because one could end up reading on one thread and then writing back on another when the asynchronous code resumes. There's a similar restriction for actor-isolated state not being allowed to be passed inout
across isolation boundaries.
Doug