Why `nonisolated lazy var` is allowed on actor?

If trying to make var declared on actor nonisolated, Swift 6 compiler of course produces error:
"'nonisolated' cannot be applied to mutable stored properties".

That is clear because such declaration would create shared mutable state which Swift 6 doesn't allow.

However for some reason it allows such kind of declaration if variable is lazy. The following code compiles and runs with Swift 6 compiler:

actor A {
    var i = 0
    nonisolated lazy var k = 0
    
    nonisolated func readK() -> Int {
        k += 1
        return k
    }
}

lazy affects only how variable is initialized, however following mutations behave the same as if it would be declared nonisolated var k = 0, which is not allowed by the compiler.

This effectively leads to possibility to create following code, which is explicit data race:

func run() {
        let a = A()
        Task.detached {
            let result = a.readK()
            print("\(result)")
        }
        Task.detached {
            let result = a.readK()
            print("\(result)")
        }
    }

I've ran this code and indeed it creates data race leading to "1" printed by both tasks.

To me this looks like a bug in the compiler and should not be allowed. Or am I missing something?

14 Likes

Ah, lazy properties.

I’m not at a computer right now so I’ll add the disclaimer that I can’t test your example, but I suspect this is a bug and the fix is to take lazy properties into account explicitly when performing this check.

A lazy property looks like a computed property to the rest of the compiler, except we also synthesize a hidden stored property of optional type to store the cached value. We then generate a getter and setter for the lazy property that accesses the underlying stored property.

If you wrote the lazy pattern out by hand, we would either complain about the underlying stored property (if it was also nonisolated) or reject the getter (if the getter was nonisolated and then it tried to access this stored property).

Property wrappers need similar treatment too, if that hasn’t been fixed already.

14 Likes

Checked with release version of Xcode 16 (16A242d), the bug is still there.

Can you please file a bug at Issues · swiftlang/swift · GitHub?

2 Likes

Absolutely. Here is the created issue It is possible to declare `nonisolated lazy var` on `actor` in Swift 6 · Issue #76513 · swiftlang/swift · GitHub

5 Likes

Thanks! Here's the fix: Concurrency: Reject nonisolated lazy properties by slavapestov · Pull Request #76518 · swiftlang/swift · GitHub

8 Likes