So, I was trying to create a package, that receives some injected information at runtime and then offers all kinds of properties that get populated depending on that injected information.
While I was trying to make it work with Swift 6 (static vars are not allowed because of data-races), I ended up with something like this:
public actor TargetConfiguration
{
enum Target
{
case target1
case target2
case target3
}
static var target: Target = .target1
static func set(newTarget: Target)
{
target = newTarget
}
static var baseColorInternal: Color
{
switch target {
case .target1:
return .red
case .target2:
return .blue
case .target3:
return .green
}
}
nonisolated
static var baseColor: Color
{
baseColorInternal
}
}
The usage would look like this:
@Test func example()
{
#expect(TargetConfiguration.baseColor == .red)
TargetConfiguration.set(newTarget: .target2)
let color = TargetConfiguration.baseColor
#expect(color == .blue)
}
Especially considering how much trouble we went through in some cases to get rid of these Swift 6 warnings / errors, I don't quite understand how this is allowed.
I assume the private static var target produces no warning, because it is isolated within the actor - alright.
The private computed property baseColorInternal is fine because it also is isolated within the actor - alright.
But why can the non isolated static var baseColor access the former baseColorInternal without issue? It is accessing the isolated var from a non isolated context, isn't it?
I also noticed I can remove non isolated from baseColor and somehow the access in the tests still work just fine - no warning or anything!
It almost seems like non of these statics are isolated within the actor, but the compiler doesn't notice it.
Can I just assume it to be race-condition free and good to go for Swift6? It produces no warnings and no errors but it feels fishy.
hi @Tolga, and welcome to the swift forums. you're correct to wonder about this behavior â this issue was raised somewhat recently in this post, after which it was diagnosed as an implementation bug. i think this should be fixed in the upcoming 6.2 compiler release, where mutable statics within actors should be treated in the same manner as mutable global variables (namely, they require a global actor isolation annotation, or must in some other way be Sendable).
Maybe a small follow up question if this is allowed here (otherwise just close this thread):
Is there any way to make this actor (or struct) have a global static var that is used within the init and have both, the init and the access to the properties nonisolated? I could not find a way without resorting to our own thread safe classes that are unchecked sendable.
my apologies, but i'm not sure i entirely follow what your end goal is. perhaps you could share a snippet of the sort of code you want to write when you have a chance.
Set a static property (again, without thread limitations)
Trigger no Swift 6 warnings :D
I also would not mind if this was an instance, but just using static getter / setter seemed nicer. More important is to archive something that is not limited to specific threads.
if the static values are to be read & written from arbitrary threads, and synchronous access is necessary, then they should probably be wrapped in a synchronization tool of some form[1]. depending on your deployment target you'll likely want to try using Mutex[2] which will ensure access to the shared mutable state is properly synchronized. e.g. something like:
public actor TargetConfiguration
{
enum Target
{
case target1
case target2
case target3
}
static let target = Mutex<Target>(.target1)
static func set(newTarget: Target)
{
target.withLock { $0 = newTarget }
}
// ...
}
i'm not sure if this suggestion is meaningfully different from what you've already tried however.
alternatively, explicitly marking them as nonisolated(unsafe) may be used as a means of opting-out of the isolation safety checks. as the keyword name implies, this comes with its own risks. âŠī¸
or OSAllocatedUnfairLock on Darwin platforms that have earlier deployment targets âŠī¸
You basically confirmed what we agreed to go with internally, which serves as a nice morale boost that we were on the right track!
But instead of Mutex we use some internal thread safe container, because we are not allowed to use iOS 18, yet.